// Open SanAndreas
// (c) 2006-2008 Stanislav "listener" Golovin
// 
// Script Engine
#include <stdafx.h>
#include <assert.h>
#include "ogMath.h"
#include "ogRW.h"
#include "ogMemory.h"
#include "ogStreaming.h"
#include "ogGameObjects.h"
#include "ogAI.h"
#include "ogScript.h"

//

// TODO: combine as CScriptEngine fields
ScriptParam	* opcodeParams = (ScriptParam *)0xA43C78;
char * _A444B0 = (char *)0xA444B0;
char * missionLoaded = (char *)0xA444B1;
WORD * _A447F4 = (WORD *)0xA447F4;	// used in CScriptThread::parseThread
WORD * _A447F8 = (WORD *)0xA447F8;	// unused ?
WORD * _A44B5C = (WORD *)0xA44B5C;	// TODO: textSpritesCount;
BYTE * textDrawEnabled = (BYTE *)0xA44B67;	// TODO: initTextDraw ?
WORD * currentTextDrawer = (WORD *)0xA44B68;	// TODO: textDrawersCount ?
CFlippedCheck * flippedCheck = (CFlippedCheck *)0xA4892C;
ScriptParam * scriptLocals = (ScriptParam *)0xA48960;
BYTE * scmBlock = (BYTE *)0xA49960;
CScriptThread ** inactiveThreadQueue = (CScriptThread **)0xA8B428;
CScriptThread ** activeThreadQueue = (CScriptThread **)0xA8B42C;
CScriptThread * scriptThreads = (CScriptThread *)0xA8B430;
CMissionCleanupList * missionCleanupList = (CMissionCleanupList *)0xA90850;
CStuckCheck * stuckCheck = (CStuckCheck *)0xA90AB0;
CTextDrawer * textDrawers = (CTextDrawer *)0xA913E8;
CTextSprite * textSprites = (CTextSprite *)0xA92D68;

float * _B7CB58 = (float *)0xB7CB58;
char * styledText1 = (char *)0x0BAACC0;
char * styledText2 = (char *)0x0BAAD40;
float * _unk_FPS = (float *)0xB7CB5C;

typedef BYTE (__thiscall * OpcodeHandler)(CScriptThread * _this, WORD opcode);

OpcodeHandler	opcodeHandlers[27] = {
	(OpcodeHandler)0x465E60,	// 00
	(OpcodeHandler)0x466DE0,	// 01
	(OpcodeHandler)0x469390,	// 02
	(OpcodeHandler)0x47C100,	// 03
	(OpcodeHandler)0x47D210,	// 04
	(OpcodeHandler)0x47E090,	// 05
	(OpcodeHandler)0x47F370,	// 06
	(OpcodeHandler)0x47FA30,	// 07
	(OpcodeHandler)0x481300,	// 08
	(OpcodeHandler)0x483BD0,	// 09
	(OpcodeHandler)0x489500,	// 10
	(OpcodeHandler)0x48A320,	// 11
	(OpcodeHandler)0x48B590,	// 12
	(OpcodeHandler)0x48CDD0,	// 13
	(OpcodeHandler)0x48EAA0,	// 14
	(OpcodeHandler)0x490DB0,	// 15
	(OpcodeHandler)0x493FE0,	// 16
	(OpcodeHandler)0x496E00,	// 17
	(OpcodeHandler)0x46D050,	// 18
	(OpcodeHandler)0x46B460,	// 19
	(OpcodeHandler)0x472310,	// 20
	(OpcodeHandler)0x470A90,	// 21
	(OpcodeHandler)0x474900,	// 22
	(OpcodeHandler)0x4762D0,	// 23
	(OpcodeHandler)0x478000,	// 24
	(OpcodeHandler)0x47A760,	// 25
	(OpcodeHandler)0x479DA0		// 26
};

//
void (__thiscall *missionCleanup__process)(CMissionCleanupList * _this) = (void (__thiscall *)(CMissionCleanupList *))0x4652D0;
void (__thiscall *flipped__process)(CFlippedCheck *_this) = (void (__thiscall *)(CFlippedCheck *))0x4655E0;
void (__thiscall *stuck__process)(CStuckCheck * _this) = (void (__thiscall *)(CStuckCheck *))0x465680;
void (__thiscall * thread__parseThread)(CScriptThread * _this) = (void (__thiscall *)(CScriptThread *))0x469F00;
void (__cdecl * processExternalScriptTriggers)() = (void (__cdecl *)())0x46CF00;
void (__cdecl * drawScriptSpheres)() = (void (__cdecl *)())0x4810E0;
void (__cdecl * processSearchlights)() = (void (__cdecl *)())0x4939F0;
void (__cdecl * updateLoadingBar)() = (void (__cdecl *)())0x590D00;	// TODO: а правильно ли эта функция называется? Возможно это updateScreen ?
void (__thiscall * intelligence_602050)(void * _this, int, int) = (void (__thiscall *)(void *, int, int))0x602050;	// TODO: CPedIntelligence method
void (__thiscall * tasks_681AF0)(void * _this, CTaskBase * task, int, int) = (void (__thiscall *)(void *, CTaskBase *, int, int))0x681AF0;	// CPedTasks method
void (__cdecl * script_clearSpaceForMissionEntity)(D3DVECTOR * pos, CEntity * entity) = (void (__cdecl *)(D3DVECTOR *, CEntity *))0x486B00;	// TODO: CScriptEngine::clearSpaceForMissionEntity
//
void (*clearJumptable)() = (void (__cdecl *)())0x470370;	// TODO: CScriptEngine::clearJumptable or CScriptEngine::CJumpTable::clear
void (__thiscall * thread_485A50)(CScriptThread * _this) = (void (__thiscall *)(CScriptThread *))0x485A50;	// TODO: CScriptThread::doWastedBustedCheck
void (__thiscall * thread_setNumberParams)(CScriptThread * _this, int count) = (void (__thiscall *)(CScriptThread *, int))0x464370;
void (__thiscall * thread__init)(CScriptThread * _this) = (void (__thiscall *)(CScriptThread *))0x4648E0;	// TODO: CScriptThread::init
void (__thiscall * thread__getThreadParams)(CScriptThread * _this, CScriptThread * newThread) = (void (__thiscall *)(CScriptThread *, CScriptThread *))0x464500;	// TODO: CScriptThread::getThreadParams
void (__thiscall * thread__endThread)(CScriptThread * _this) = (void (__thiscall *)(CScriptThread *))0x465AA0;	// TODO: CScriptThread::endThread
BYTE (*isNeedSkipCutscene)() = (BYTE (__cdecl *)())0x4D5D10;
//
void (*_409D10)(int index, char * txdName, int flags) = (void (__cdecl *)(int, char *, int))0x409D10;	// TODO: requestActorModel ?
void (*loadRequestedModels)(int) = (void (__cdecl *)(int))0x40EA10;
void (*shakeCamera)(CCamera * cam, float duration) = (void (__cdecl *)(CCamera *, float))0x50A970;
void (*createPlayerPed)(int playerNum) = (void (__cdecl *)(int))0x60D790;
void (__thiscall * _5E47E0)(CPed * _this, BYTE) = (void (__thiscall *)(CPed *, BYTE))0x5E47E0;
void (* world_remove)(CEntity * entity) = (void (__cdecl *)(CEntity *))0x563280;	// TODO: CWorld::remove
float (*world_getGroundZ)(float x, float y) = (float (__cdecl *)(float, float))0x569660;	// TODO: CWorld::getGroundZ
float (__thiscall * entity__getDistanceFromCentreOfMassToBottom)(CEntity * _this) = (float (__thiscall *)(CEntity *))0x536BE0;
void (*_563220)(CEntity * e) = (void (__cdecl *)(CEntity *))0x563220;	// TODO: CWorld::add ?
//

//=============================================================================
// process opcodes 0-99 (0x00-0x63)
BYTE CScriptThread::opcodeHandler00 (WORD opcode) {
	ScriptParam * p1;
	ScriptParam * p2;

	CPed * ped;

	// debug ("op00: opcode %04x\n", opcode);

	switch (opcode) {
	case 0x00:	// nop
		return 0;
	case 0x01:	// wait %1d% ms
		getNumberParams (1);
		wakeTime = opcodeParams[0].dwParam + *currentTime;
		return 1;	// suspend thread
	case 0x02:	// jump %1p%
		getNumberParams (1);
		setIP (opcodeParams[0].nParam);
		return 0;
	case 0x03:	// shake_camera %1d%
		getNumberParams (1);
		shakeCamera (camera, (float)opcodeParams[0].nParam/1000.0f);
		return 0;
	case 0x04:	// %1d% = %2d%
	case 0x05:
	case 0x06:
	case 0x07:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->dwParam = opcodeParams[0].dwParam;
		return 0;
	case 0x08:	// %1d% += %2d%	
	case 0x0A:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->dwParam += opcodeParams[0].dwParam;
		return 0;
	case 0x09:	// %1d% += %2d%	(float)
	case 0x0B:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->fParam += opcodeParams[0].fParam;
		return 0;
	case 0x0C:	// %1d% -= %2d%	
	case 0x0E:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->dwParam -= opcodeParams[0].dwParam;
		return 0;
	case 0x0D:	// %1d% -= %2d%	(float)
	case 0x0F:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->fParam -= opcodeParams[0].fParam;
		return 0;
	case 0x10:	// %1d% *= %2d%
	case 0x12:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->dwParam *= opcodeParams[0].dwParam;
		return 0;
	case 0x11:	// %1d% *= %2d% (float)
	case 0x13:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->fParam *= opcodeParams[0].fParam;
		return 0;
	case 0x14:	// %1d% /= %2d%
	case 0x16:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->dwParam /= opcodeParams[0].dwParam;
		return 0;
	case 0x15:	// %1d% /= %2d% (float)
	case 0x17:
		p1 = getPointerParam (2);
		getNumberParams (1);
		p1->fParam /= opcodeParams[0].fParam;
		return 0;
	case 0x18:	// %1d% > %2d%
	case 0x19:
		p1 = getPointerParam (2);
		getNumberParams (1);
		setCondResult ((p1->nParam > opcodeParams[0].nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x1A:	// %1d% > %2d%
	case 0x1B:
		getNumberParams (1);
		p1 = getPointerParam (2);
		setCondResult ((opcodeParams[0].nParam > p1->nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x1C:	// %1d% > %2d%
	case 0x1D:
	case 0x1E:
	case 0x1F:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->nParam > p2->nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x20:	// %1d% > %2d% (float)
	case 0x21:
		p1 = getPointerParam (2);
		getNumberParams (1);
		setCondResult ((p1->fParam > opcodeParams[0].fParam) ? 1 : 0);
		return 0;	
	case 0x22:	// %1d% > %2d% (float)
	case 0x23:
		getNumberParams (1);
		p1 = getPointerParam (2);
		setCondResult ((opcodeParams[0].fParam > p1->fParam) ? 1 : 0);
		return 0;
	case 0x24:	// %1d% > %2d% (float)
	case 0x25:
	case 0x26:
	case 0x27:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->fParam > p2->fParam) ? 1 : 0);	
		return 0;
	case 0x28:	// %1d% >= %2d%
	case 0x29:
		p1 = getPointerParam (2);
		getNumberParams (1);
		setCondResult ((p1->nParam >= opcodeParams[0].nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x2A:	// %1d% >= %2d%
	case 0x2B:
		getNumberParams (1);
		p1 = getPointerParam (2);
		setCondResult ((opcodeParams[0].nParam >= p1->nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x2C:	// %1d% >= %2d%
	case 0x2D:
	case 0x2E:
	case 0x2F: 
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->nParam >= p2->nParam) ? 1 : 0);	// NOTE: signed int!
		return 0;
	case 0x30:	// %1d% >= %2d% (float)
	case 0x31:
		p1 = getPointerParam (2);
		getNumberParams (1);
		setCondResult ((p1->fParam >= opcodeParams[0].fParam) ? 1 : 0);
		return 0;	
	case 0x32:	// %1d% >= %2d% (float)
	case 0x33:
		getNumberParams (1);
		p1 = getPointerParam (2);
		setCondResult ((opcodeParams[0].fParam >= p1->fParam) ? 1 : 0);
		return 0;
	case 0x34:	// %1d% >= %2d% (float)
	case 0x35:
	case 0x36:
	case 0x37:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->fParam >= p2->fParam) ? 1 : 0);	
		return 0;
	case 0x38:	// %1d% == %2d%
	case 0x39:
		p1 = getPointerParam (2);
		getNumberParams (1);
		setCondResult ((p1->dwParam == opcodeParams[0].dwParam) ? 1 : 0);
		return 0;	
	case 0x3A:	// %1d% == %2d%
	case 0x3B:
	case 0x3C:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->dwParam == p2->dwParam) ? 1 : 0);	
		return 0;
	case 0x42:	// %1d% == %2d% (float)
	case 0x43:
		p1 = getPointerParam (1);
		getNumberParams (1);
		setCondResult ((p1->fParam == opcodeParams[0].fParam) ? 1 : 0);
		return 0;
	case 0x44:	// %1d% == %2d% (float)
	case 0x45:
	case 0x46:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		setCondResult ((p1->fParam == p2->fParam) ? 1 : 0);	
		return 0;
	case 0x4D:	// jump_if_false %1p%
		getNumberParams (1);
		if (!condResult)
			setIP (opcodeParams[0].nParam);
		return 0;
	case 0x4E:	// end_thread
		if (_fDC)
			*missionLoaded = 0;
		removeFromQueue (activeThreadQueue);
		addToQueue (inactiveThreadQueue);
		thread__endThread (this);	// TODO: end thread
		return 1;
	case 0x4F:	// create_thread
		getNumberParams (1);
		if (opcodeParams[0].nParam < 0)
			debug ("NOTE: negative parameter in create_thread\n");
		thread__getThreadParams (this, script_allocThread (scmBlock+(opcodeParams[0].nParam >= 0 ? opcodeParams[0].nParam : opcode)));	// WTF! scmBlock+opcode ?
		return 0;
	case 0x50:	// gosub %1p%
		getNumberParams (1);
		stack[sp++] = ip;	// TODO: check stack overflow
		setIP (opcodeParams[0].nParam);
		return 0;
	case 0x51:	// return
		sp--;
		ip = stack[sp];	// TODO: check stack underflow
		return 0;
	case 0x52:	// NOP (6 params)
		getNumberParams (6);
		return 0;
	case 0x53:	// %5d% = create_player %1o% at %2d% %3d% %4d%
		getNumberParams (4);
		if (loadedModelInfo[0]._f10 != 1) {
			_409D10 (0, "player", 0x1A);	// TODO: requestActorModel ?
			loadRequestedModels (1);
		}
		assert (opcodeParams[0].nParam == 0);	// only one player supported
		createPlayerPed (0);	
		ped = player->pPed;
		_5E47E0 (ped, 2);
		world_remove (ped);
				
		D3DVECTOR pos;
		pos.x = opcodeParams[1].fParam;
		pos.y = opcodeParams[2].fParam;
		pos.z = opcodeParams[3].fParam;
		if (pos.z <= 100.0f) 
			pos.z = world_getGroundZ (pos.x, pos.y);
		pos.z += entity__getDistanceFromCentreOfMassToBottom (ped);
		player->pPed->setPos (pos.x, pos.y, pos.z);
		script_clearSpaceForMissionEntity (&pos, ped);		
		_563220 (ped);
		opcodeParams[0].nParam = 0;	// player number
		thread_setNumberParams (this, 1);
		tasks_681AF0 ((BYTE *)(ped->intelligence)+4, new CTask_8708EC (), 4, 0);	// TODO: player->pPed->intelligence->tasks->....
		return 0;
	case 0x58:	// %1d% += %2d%
	case 0x5A:
	case 0x5C:
	case 0x5E:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->nParam += p2->nParam;
		return 0;
	case 0x59:	// %1d% += %2d% (float)
	case 0x5B:
	case 0x5D:
	case 0x5F:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam += p2->fParam;
		return 0;
	case 0x60:	// %1d% -= %2d%
	case 0x62:
	case 0x64:
	case 0x66:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->nParam -= p2->nParam;
		return 0;
	case 0x61:	// %1d% -= %2d% (float)
	case 0x63:
	case 0x65:
	case 0x67:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam -= p2->fParam;
		return 0;
	case 0x68:	// %1d% *= %2d%
	case 0x6A:
	case 0x6C:
	case 0x6E:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->nParam *= p2->nParam;
		return 0;
	case 0x69:	// %1d% *= %2d% (float)
	case 0x6B:
	case 0x6D:
	case 0x6F:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam *= p2->fParam;
		return 0;
	case 0x70:	// %1d% /= %2d%
	case 0x72:
	case 0x74:
	case 0x76:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->nParam *= p2->nParam;
		return 0;
	case 0x71:	// %1d% /= %2d% (float)
	case 0x73:
	case 0x75:
	case 0x77:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam /= p2->fParam;
		return 0;
	case 0x78: // %1d% += unknown_float * %2d% ; (float)
	case 0x79:
		p1 = getPointerParam (1);
		getNumberParams (1);
		p1->fParam += opcodeParams[0].fParam * (*_unk_FPS);
		return 0;
	case 0x7A:	// %1d% += unknown_float * %2d% ; (float)
	case 0x7B:
	case 0x7C:
	case 0x7D:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam += p2->fParam * (*_unk_FPS);
		return 0;
	case 0x7E: // %1d% -= unknown_float * %2d% ; (float)
	case 0x7F:
		p1 = getPointerParam (1);
		getNumberParams (1);
		p1->fParam -= opcodeParams[0].fParam * (*_unk_FPS);
		return 0;
	case 0x80:	// %1d% -= unknown_float * %2d% ; (float)
	case 0x81:
	case 0x82:
	case 0x83:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam -= p2->fParam * (*_unk_FPS);
		return 0;
	case 0x84:	// %1d% = %2d%
	case 0x85:
	case 0x86:
	case 0x87:
	case 0x88:
	case 0x89:
	case 0x8A:
	case 0x8B:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->dwParam = p2->dwParam;
		return 0;
	case 0x8C:	// %1d% = float %2d% to_integer
	case 0x8E:
	case 0x90:
	case 0x92:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->nParam = (int)(p2->fParam);
		return 0;
	case 0x8D:	// integer %2d% to_float
	case 0x8F:
	case 0x91:
	case 0x93:
		p1 = getPointerParam (1);
		p2 = getPointerParam (1);
		p1->fParam = (float)(p2->nParam);
		return 0;
	case 0x94:	// make %1d% absolute_integer
	case 0x95:
		p1 = getPointerParam (1);
		if (p1->nParam < 0)
			p1->nParam = -(p1->nParam);
		return 0;
	case 0x96:	// make %1d% absolute_float
	case 0x97:
		p1 = getPointerParam (1);
		p1->fParam = fabsf (p1->fParam);
		return 0;
	case 0x98:	// random_float_in_ranges_0.0_to_0.5
		p1 = getPointerParam (1);
		rand ();
		rand ();
		rand ();
		p1->fParam = (float)rand()/65536.0f;
		return 0;
	case 0x99:	// %1d% = random_int_in_ranges_0_to_32767
		p1 = getPointerParam (1);
		p1->nParam = rand ();
		return 0;
	case 0x9A:	// %6d% = create_actor_pedtype %1d% model %2o% at %3d% %4d% %5d%
		// TODO: этот фрагмент кода используется еще в нескольких местах. Возможно, стоит вынести его в отдельный метод?
		getNumberParams (5);
		if (opcodeParams[0].nParam == 6) {	// cop
			patchCopModelId (opcodeParams[0].nParam, &(opcodeParams[1].nParam));
		} else if (opcodeParams[0].nParam == 18 || opcodeParams[0].nParam == 19) {	// medic or fireman
		} else {	// normal ped
		}
		return 0;
	}

	return 0xFF;	// 003D, 003E, 003F, 0040, 0041, 0047, 0048, 0049, 004A, 004B, 004C, 0054, 0055, 0056, 0057; 
					// 009C, 009D, 009E, 009F, 00A2, 00AC, 00B2, 00B3, 00B4, 00B5, 00B6, 00B7, 00B8, 00B9, 00BD
}
//=============================================================================
void CScriptThread::getNumberParams (int count) {
	ScriptParam * p = opcodeParams;
	DWORD idx, idx2;
	short idx3;	
	
	// debug (" >> getNumberParams (%d)\n", count);
	for (int i = 0; i < count; i++, p++) {
		BYTE type = *ip++;
		switch (type) {
			case 1:	// immediate signed 32-bit
			case 6:
				p->dwParam = *(DWORD *)ip;
				ip += 4;
				// debug (" >> IP = %08x, type = %d: IMM32: %08x\n", ip-5, type, opcodeParams[i].dwParam);
				break;
			case 2:	// 16 bit
				idx = *(WORD *)ip;
				ip += 2;
				p->dwParam = *(WORD *)(scmBlock+idx);				
				break;
			case 3:	// 32 bit
				idx = *(WORD *)ip;
				ip += 2;
				p->dwParam = _fDC ? scriptLocals[idx].dwParam : tls[idx].dwParam;
				break;
			case 4:	// immediate signed 8 bit
				p->nParam = *(char *)ip;
				ip++;
				break;
			case 5:	// immediate signed 16 bit
				p->nParam = *(short *)ip;
				ip += 2;
				break;
			case 7:					
				idx = *(WORD *)ip;
				idx2 = *(WORD *)(ip+2);
				idx3 = *(short *)(ip+4);
				ip += 6;
		
				if (idx3 < 0) 
					idx2 = *(DWORD *)(scmBlock+idx2);
				else 
					idx2 = _fDC ? scriptLocals[idx2].dwParam : tls[idx2].dwParam;

				p->dwParam = *(DWORD *)(scmBlock+idx+4*idx2);
				break;
			case 8:
				idx = *(WORD *)ip;
				idx2 = *(WORD *)(ip+2);
				idx3 = *(short *)(ip+4);
				ip += 6;
				
				if (idx3 < 0) 
					idx2 = *(DWORD *)(scmBlock+idx2);
				else 
					idx2 = _fDC ? scriptLocals[idx2].dwParam : tls[idx2].dwParam;
				
				p->dwParam = _fDC ? scriptLocals[idx+idx2].dwParam : tls[idx+idx2].dwParam;
				break;
		}
	}
}

void CScriptThread::getArrayIndex(int move, WORD *pOff, DWORD *pIdx) {
	*pOff = *(WORD *)ip;
	WORD i1 = *(WORD *)(ip+2);
	short i2 = *(short *)(ip+4);
	
	if (i2 < 0) 
		*pIdx = *(DWORD *)(scmBlock+i1);
	else 
		*pIdx = _fDC ? scriptLocals[i1].dwParam : tls[i1].dwParam;
	if (move)
		ip += 6;
}

ScriptParam * CScriptThread::localArrayPtr (WORD off, DWORD idx, int mul) {	
	return (_fDC ? scriptLocals : tls)+idx*mul+off;
}

ScriptParam * CScriptThread::getPointerParam (int t) {	// parameter ignored
	DWORD idx;
	WORD off;
		
	BYTE type = *ip++;
	switch (type) {
	case 2:		// 0
	case 10:
	case 16:
		idx = *(WORD *)ip;
		ip += 2;
		return (ScriptParam *)(scmBlock+idx);		
	case 3:		// 1
	case 11:
	case 17:
		idx = *(WORD *)ip;
		ip += 2;
		return _fDC ? scriptLocals+idx : tls+idx;		
	case 7:		// 2
	case 12:
	case 18:
		getArrayIndex (1, &off, &idx);
		if (type == 18)
			return (ScriptParam *)(scmBlock+off+idx*16);
		return (ScriptParam *)(scmBlock+off+idx*(type == 12 ? 8 : 4));		
	case 8:
	case 13:
	case 19:
		getArrayIndex (1, &off, &idx);
		if (type == 19)
			return localArrayPtr (off, idx, 4);
		return localArrayPtr (off, idx, type == 13 ? 2 : 1);		
	}
	return NULL;
}

void CScriptThread::setCondResult (BYTE result) {
	if (notFlag)
		result = !result;

	result &= 1;	// NOTE: на всякий случай

	if (!logicalOp) {	
		condResult = result;
		return;
	}

	if (logicalOp <= 8) {
		condResult &= result;
		logicalOp--;
	} else if (logicalOp >= 0x15 && logicalOp <= 0x1C) {
		condResult |= result;
		logicalOp = logicalOp == 0x15 ? 0 : logicalOp-1;
	}
}

void CScriptThread::addToQueue (CScriptThread ** queue) {
	next = *queue;
	prev = NULL;
	if (next)
		next->prev = this;
	*queue = this;
}

void CScriptThread::removeFromQueue (CScriptThread ** queue) {
	if (prev) 
		prev->next = next;
	else 
		*queue = next;
	if (next)
		next->prev = prev;
}

void CScriptThread::setIP (int addr) {
	ip = addr >= 0 ? scmBlock+addr : baseIp-addr;
}

void CScriptThread::patchCopModelId(int pedType, int *modelId) {
	if (pedType != 6)
		return;

	switch (*modelId) {
	case 280:	// lapd1
	case 281:	// sfpd1
	case 282:	// lvpd1
	case 284:	// lapdm1
		*modelId = 0;
		return;
	case 283:	// csher
		*modelId = 7;
		return;
	case 285:	// swat
		*modelId = 2;
		return;
	case 286:	// fbi
		*modelId = 4;
		return;
	case 287:	// army
		*modelId = 5;
	}
}

void CScriptThread::parseThread() {
	if (sceneSkip && isNeedSkipCutscene ()) {
		styledText2[0] = '\0';
		ip = sceneSkip < 0 ? baseIp-sceneSkip : scmBlock+sceneSkip;
		sceneSkip = 0;
		wakeTime = 0;
	}
	
	if (_fC6)
		thread_485A50 (this);
	if (_fDC && *_A444B0 == 1) {
		while (sp > 1)
			sp--;	// TODO: а не проще сразу сказать sp = 1 ?
		if (sp != 1) {
			sp = 0;
			ip = stack[0];
		}
	}
	clearJumptable ();

	if (*currentTime < wakeTime)
		return;	// sleeping thread

	BYTE done;
	do {
		(*_A447F4)++;	// TODO: предположительно, это количество инструкций, выполненных в текущем кадре. Использовалось для отладчика

		// get next opcode
		WORD opcode = *(WORD *)ip;
		ip += 2;

		notFlag = (opcode >> 15)&1;
		opcode &= 0x7FFF;	// TODO: вставить проверку на максимальное значение опкода
		// debug ("IP: %08x, Opcode: %s %04x\n", ip-2, notFlag ? "(!)":"   ", opcode);
		done = opcode < 0x9A ? opcodeHandler00 (opcode) : opcodeHandlers[opcode/100] (this, opcode);
	} while (!done);
}

//=============================================================================
CScriptThread * script_allocThread (BYTE * startIp) {
	CScriptThread * t = *inactiveThreadQueue;	// TODO: check queue is not empty
	t->removeFromQueue (inactiveThreadQueue);
	thread__init (t);	// TODO: t->init
	t->ip = startIp;
	t->addToQueue (activeThreadQueue);
	t->isActive = 1;
	return t;
}
//-----------------------------------------------------------------------------
// main script processing method 
// основной метод обработки скриптов.
// вызывается один раз на кадр
void script_process () {	// TODO: CScriptEngine::process
	if (*replayInProgress)
		return;

	(*_A447F4) = 0;
	(*_A447F8) = 0;

	flipped__process (flippedCheck);	// TODO: flippedCheck->process (); or scriptEngine->flipped->process ();
	stuck__process (stuckCheck);	// TODO: stuckCheck->process (); or scriptEngine->stuck->process ();
	missionCleanup__process (missionCleanupList);	// TODO: missionCleanupList->process (); or scriptEngine->cleanupList->process ();
	drawScriptSpheres ();	// TODO: scriptEngine->drawScriptSpheres ();
	processSearchlights ();	// TODO: scriptEngine->processSearchlights ();

	if (*_A444B0 > 0) 
		(*_A444B0)--;
	if (*textDrawEnabled) {
		for (int i = 0; i < 0x60; i++) {
			CTextDrawer * td = textDrawers+i;

			td->letterWidth = 0.48f;
			td->letterHeight = 1.12f;
			td->color.color = 0xFFE1E1E1;
			td->_fC = 0;
			td->centered = 0;
			td->withBackground = 0;
			td->_fF = 0;
			td->lineHeight = 182.0f;
			td->lineWidth = 640.0f;
			td->backgroundBoxColor.color = 0x80808080;
			td->proportional = 1;
			td->backgroundColor.color = 0xFF000000;
			td->shadowType = 2;
			td->outlineType = 0;
			td->_f23 = 0;
			td->alignRight = 0;
			td->font = 1;
			td->xPosition = 0;
			td->yPosition = 0;
			memset (td->gxtEntry, 0, 8);
			td->param1 = 0xFFFFFFFF;
			td->param2 = 0xFFFFFFFF;
		}
		*currentTextDrawer = 0;	// textDrawerCount ?
		for (int i = 0; i < 0x80; i++) {
			textSprites[i].type = 0;
			textSprites[i]._f4 = 0;
		}
		*_A44B5C = 0;	// TODO: textSpriteCount ?
		if (*textDrawEnabled == 1)	// TODO: а это нужно?
			*textDrawEnabled = 0;
	}

	// update script timers
	int _v10 = (int)(*_B7CB58/50.0f*1000.0f);
	scriptLocals[32].dwParam += _v10;
	scriptLocals[33].dwParam += _v10;

	updateLoadingBar ();
	for (CScriptThread * currThread = *activeThreadQueue; currThread; currThread = currThread->next) {
		currThread->tls[32].dwParam += _v10;
		currThread->tls[33].dwParam += _v10;
		/// thread__parseThread (currThread);
		currThread->parseThread ();
		if (currThread->next && !(currThread->next->isActive))
			break;
	}
	updateLoadingBar ();

	for (int i = (*pedPool)->size; i > 0; i--) {
		CPed * p = (CPed *)((*pedPool)->at (i-1));	
		if (!p)
			continue;
		if (p->_f484 == 2)	// scripted ped ?
			intelligence_602050 (p->intelligence, 0, 0);
	}
}