#include "driver2.h"
#include "event.h"
#include "cars.h"
#include "players.h"
#include "mc_snd.h"
#include "camera.h"
#include "convert.h"
#include "draw.h"
#include "models.h"
#include "mission.h"
#include "job_fx.h"
#include "bomberman.h"
#include "debris.h"
#include "main.h"
#include "system.h"
#include "spool.h"
#include "texture.h"
#include "civ_ai.h"
#include "pedest.h"
#include "shadow.h"
#include "gamesnd.h"
#include "dr2roads.h"
#include "ASM/rndrasm.h"
#include "cutrecorder.h"

struct FixedEvent // same as EVENT but different fields
{
	VECTOR position;
	short rotation;
	short active;
	u_short initialRotation;
	u_short finalRotation;
	u_short minSpeed;
	u_short maxSpeed;
	short flags;
	short radius;
	int model;
	EVENT* next;
	char* modelName;
};

struct MissionTrain
{
	EVENT* engine;
	int* node;
	int cornerSpeed;
	int initialStraightSpeed;
	int finalStraightSpeed;
	int start;
	int startDir;
};

struct Foam
{
	MODEL* model;
	int rotate;
};

struct EventCarriage
{
	short rotation;
	short vel;
};

struct MultiCar
{
	EVENT* event;
	int count;
};

struct Helicopter
{
	int speed;
	short pitch;
	short dp;
	short roll;
	short dr;
	int lastX;
	int lastZ;
	TEXTURE_DETAILS rotorTexture;
	short rotorrot;
	short rotorvel;
	int cleanModel;
	int deadModel;
};

struct Detonator
{
	int timer;
	int count;
};

struct CameraDelay
{
	int delay;
	int type;
};

enum VisType
{
	VIS_INIT = 0,
	VIS_SORT = 1,
	VIS_ADD = 2,
	VIS_NEXT = 3,
};

struct EventCamera
{
	VECTOR position;
	short yAng;
	MATRIX matrix;
	int rotate;
};

enum Station
{
	EVENT_NO_STATION = 0,
	EVENT_APPROACHING = 1,
	EVENT_LEAVING = 2,
};

#define PATH_NODE_WRAP		0x80000000	// go back to first node without interpolation
#define PATH_NODE_CYCLE		0x80000001	// cycle nodes with interpolation
#define PATH_NODE_STATION	0x80000002	// stop point

#define PATH_NODE_REVERSE	0x80000003

// TODO: put more meaning into those arrays

int ElTrainData[83] = {
	6, // train count

	// train 1 (n 1)
	80, 130, 0x8000,	// speed 1, 2, start direction
	336284, -220364, 283420, PATH_NODE_STATION,
	-204500, -158924, 247580, -123084, 188624, -158924, 73520, -138444, 17200, -124148, -39120, PATH_NODE_STATION,
	-109276, -82131, -80103, -17628, -203568, -124712, -39728, -265000, 129620, -386012, PATH_NODE_WRAP,

	// train 2 (n 31)
	80, 130, 0,
	-158928, 189219, -123684, 246995, PATH_NODE_CYCLE,

	// train 3 (n 39)
	0, 90, 0x8000,
	188402, -425768, 354291, PATH_NODE_WRAP,

	// train 4 (n 46)
	0, 90, 0x8000,
	354291, -425168, 188402, PATH_NODE_WRAP,

	// train 5 (n 53)
	60, 110, 0,
	-386012, 130215, -264404, -39132, -124688, 16619, -139048, 72943, -159520, 282863, PATH_NODE_STATION,
	-204991, -220964, 336284, PATH_NODE_WRAP,

	// train 6 (n 71)
	70, 120, 0,
	-82719, -39712, PATH_NODE_STATION,
	-115487, -124120, -202968,-18216, -80683, PATH_NODE_CYCLE
};

int VegasTrainData[7] = {
	0, 123, 0x8000,
	982000, -68855, 762717, PATH_NODE_WRAP
};

int VegasParkedTrains[3] = {
	-68855, 975520, 792317
};

// [A] monorail
int VegasMonorailData[] = {
	20, 70, 0x18000, // cycle flag. Cannot be used for event itself
	-70500, -67510, -111000, PATH_NODE_REVERSE, -70500, PATH_NODE_WRAP,
};

int HavanaFerryData[12] = {
	40, 0, 64, 425555, -452, 20000,		// Ferry 1
	25, 0, 64, 315750, 130740, 135960	// Ferry 2
};

int RioFerryData[6] = {
	6, 0, 0, 200384, 387424, 410000
};

int HavanaMiniData[4] = {
	50, 3995, 4605, 0
};

int LiftingBridges[55] = {
	8,	// bridge count

	// 1
	-182784, -175616, -168448,
	7, -227328, -162304, -141824, -121344, -100864, -80384, -59904,

	0x100, // goose island start

	// 2
	-312832, -305664, -298496, 
	1, 324096,

	// 3
	-311808, -304640, -297472,
	1, 247296,

	// 4
	-256512, -249344, -242176,
	1, 247296,

	// 5
	-262656, -255488, -248320,
	1, 324096,

	0x8000,	// goose island end

	// 6
	170496, 177664, 184832,
	1, -271360,

	// 7
	-12800, -5632, 1536,
	5, -162304, -102912, -82432, -61952, -39936,

	// 8
	-6656, 512, 7680,
	3, 4137, 27648, 128000
};


int ChicagoCameraHack[3] = {
	-208006, 198, 657039
};

int HavanaCameraHack[9] = {
	-491431, 3568, -139048,
	-453981, 409, -128009,
	-453975, 429, -127175
};

int VegasCameraHack[16] = {
	124092, 3988, 3494,
	-11285, 1559, 843824,
	-38801, 2681, 273148,
	-36925, 2048, 269647, 273935,
	-65503, 1617, 796775
};

int RioCameraHack[6] = {
	-276620, 3072, -321920,
	-40920, 3623, 382170
};

MissionTrain missionTrain[2] =
{
	{ 0, ElTrainData + 4, 100, 150, 200, 289051, 32768 },
	{ 0, ElTrainData + 7, 125, 145, 215, -204500, 0 }
};

FixedEvent chicagoDoor[3] =
{
  {
	{ -207616, 0, 659706, 0 },
	0,
	0,
	800u,
	0u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"WRGFLDDOOR"
  },
  {
	{ -209152, -512, 668928, 0 },
	0,
	0,
	2496u,
	4096u,
	25u,
	50u,
	64,
	0,
	0,
	0,
	"DOOR01"
  },
  {
	{ 195264, -3728, 74752, 0 },
	0,
	0,
	0u,
	0u,
	0u,
	0u,
	1088,
	0,
	0,
	0,
	"FRAME"
  }
};

FixedEvent havanaFixed[3] =
{
  {
	{ -183296, -273, -41720, 0 },
	0,
	0,
	800,
	0,
	25,
	50,
	64,
	0,
	0,
	0,
	"DOOR"
  },
  {
	{ -455168, 1529, -125440, 0 },
	0,
	0,
	0u,
	0u,
	0u,
	0u,
	1536,
	0,
	0,
	0,
	"LIFT_SHAFT"
  },
  {
	{ -487936, 0, -136689, 0 },
	0,
	0,
	1152u,
	0u,
	10u,
	20u,
	80,
	0,
	0,
	0,
	"BASEDOOR"
  }
};

FixedEvent vegasDoor[5] =
{
  {
	{ -11851, 0, 844163, 0 },
	0,
	0,
	1700u,
	3072u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"DOOR"
  },
  {
	{ -106242, -239, -216960, 0 },
	0,
	0,
	800u,
	0u,
	25u,
	50u,
	96,
	0,
	0,
	0,
	"DOOR01"
  },
  {
	{ 63058, -231, 501184, 0 },
	0,
	0,
	3200u,
	4096u,
	25u,
	50u,
	96,
	0,
	0,
	0,
	"DOOR02"
  },
  {
	{ -159356, -176, 647024, 0 },
	0,
	0,
	3200u,
	4096u,
	25u,
	50u,
	64,
	0,
	0,
	0,
	"DOOR03"
  },
  {
	{ 123936, -170, 4928, 0 },
	0,
	0,
	3200u,
	4096u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"GATE01"
  }
};


FixedEvent rioDoor[6] =
{
  {
	{ -123328, -177, -254720, 0 },
	0,
	0,
	3200u,
	4096u,
	25u,
	50u,
	96,
	0,
	0,
	0,
	"DOOR1"
  },
  {
	{ -125248, -17, -256208, 0 },
	0,
	0,
	1600u,
	0u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"DOOR2"
  },
  {
	{ -274000, -17, -321408, 0 },
	0,
	0,
	1748u,
	3072u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"GATE01"
  },
  {
	{ -274000, -17, -322432, 0 },
	0,
	0,
	2348u,
	1024u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"GATE01"
  },
  {
	{ -40432, -17, 383328, 0 },
	0,
	0,
	700u,
	2048u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"GATE03"
  },
  {
	{ -39424, -17, 383328, 0 },
	0,
	0,
	900u,
	0u,
	25u,
	50u,
	80,
	0,
	0,
	0,
	"GATE03"
  }
};

Helicopter HelicopterData =
{
  400,
  0,
  0,
  0,
  0,
  0,
  0,
  { { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }, 0u, 0u, 0, 0 },
  0,
  0,
  0,
  0
};

static Foam foam;

EventGlobal events;

CELL_OBJECT* EventCop;
int event_models_active = 0;

static EVENT(*trackingEvent[2]);
static MS_TARGET(*carEvent[8]);
static int cameraEventsActive = 0;
static CameraDelay cameraDelay;
static Detonator detonator;
static int eventHaze = 0;
static int doneFirstHavanaCameraHack = 0;
static SVECTOR boatOffset;
static FixedEvent* fixedEvent = NULL;
int carsOnBoat = 0;

MultiCar multiCar;

EVENT* firstEvent;
EVENT* firstMissionEvent;
EVENT* event;

static EventCamera eventCamera;

void MakeEventTrackable(EVENT* ev);

// [D] [T]
int GetVisValue(int index, int zDir)
{
	int camera;
	int radius;
	EVENT* ev;
	VECTOR pos;

	if (index & 0xC000)
	{
		camera = (index >> 0xf ^ 1) & 1;

		if (zDir == 0)
		{
			pos.vx = player[camera].cameraPos.vx;
			radius = 16000;
		}
		else
		{
			pos.vx = player[camera].cameraPos.vz;
			radius = 16000;
		}
	}
	else
	{
		int multiple;

		if (index & 0x80)
			ev = (EVENT*)&fixedEvent[index & 0x7f];
		else
			ev = &event[index & 0x7f];

		if (index & 0xf00)
		{
			if ((ev->flags & 0x30) && zDir == 0 || !(ev->flags & 0x30) && zDir != 0)
				multiple = 1;
			else
				multiple = 0;
		}
		else
		{
			multiple = 1;
		}

		if (multiple)
		{
			pos.vx = ev->position.vx;
			pos.vz = ev->position.vz;

			if ((ev->flags & 0xCC0) == 0x80)
			{
				pos.vx -= boatOffset.vx;
				pos.vz -= boatOffset.vz;
			}

			if (zDir != 0)
				pos.vx = pos.vz;
		}
		else
		{
			pos.vx = ev->node[(index & 0xf00U) >> 8];
		}

		radius = ev->radius;
	}

	if (index & 0x2000)
		pos.vx -= radius;

	if (index & 0x1000)
		pos.vx += radius;

	return pos.vx;
}



static unsigned short* xVis;
static unsigned short* zVis;

// [D] [T]
void VisibilityLists(VisType type, int i)
{
	static unsigned short xList[128]; // offset 0x0
	static unsigned short zList[128]; // offset 0x100
	static unsigned short(*firstX[2]); // offset 0x0
	static unsigned short(*firstZ[2]); // offset 0x8
	static int count; // offset 0x10

	static unsigned short(*list[2]) = {
		xList, zList
	};

	int k;
	EVENT* ev;
	int tempList;
	int num;
	int n, j, vis;
	int camera;
	int table[128];

	//debugCount = &count.10;
	if (type == VIS_SORT)
	{
		num = 0;
		do {
			j = 0;

			while (j < count)
			{
				vis = GetVisValue(list[num][j], num);

				table[j] = vis;

				// add sorted
				// code is similar to sorting code in DrawAllTheCars
				if (j > 0 && vis < table[j - 1])
				{
					tempList = list[num][j];

					k = j - 1;
					do {
						n = k;

						table[k + 1] = table[k];
						list[num][k + 1] = list[num][k];

						if (k == 0)
							break;

						k--;
					} while (vis < table[k]);

					table[n] = vis;
					list[num][n] = tempList;
				}

				j++;
			}
			num++;
		} while (num < 2);

		num = 0;
		while (num < NumPlayers)
		{
			if (num != 0)
				camera = 0x4000;
			else
				camera = 0x8000;

			firstX[num] = xList;
			firstZ[num] = zList;

			do {
			} while ((*(firstX[num]++) & camera) == 0);

			do {
			} while ((*(firstZ[num]++) & camera) == 0);

			num++;
		}
	}
	else if (type == VIS_INIT)
	{
		zList[0] = -0x6000;
		xList[0] = -0x6000;
		zList[1] = -0x7000;
		xList[1] = -0x7000;

		count = 2;

		if (NumPlayers == 2)
		{
			zList[2] = 0x6000;
			xList[2] = 0x6000;
			zList[3] = 0x5000;
			xList[3] = 0x5000;

			count = 4;
		}

		num = 0;
		while (num < NumPlayers)
		{
			firstX[num] = xList;
			firstZ[num] = zList;
			num++;
		}
	}
	else if (type == VIS_ADD)
	{
		if (i & 0x80)
			ev = (EVENT*)&fixedEvent[i & 0x7f];
		else
			ev = &event[i & 0x7f];

		if (ev->radius == 0)
		{
			xList[count] = i;
			zList[count] = i;

			count++;
		}
		else
		{
			xList[count] = i | 0x2000;
			zList[count] = i | 0x2000;

			xList[count + 1] = i | 0x1000;
			zList[count + 1] = i | 0x1000;

			count += 2;
		}
	}
	else if (type == VIS_NEXT)
	{
		ev = firstEvent;

		while (ev != NULL)
		{
			ev->flags &= ~4;
			ev = ev->next;
		}

		xVis = firstX[i];
		zVis = firstZ[i];
	}
}

// [D] [T]
void SetElTrainRotation(EVENT* ev)
{
	if (ev->flags & 0x8000)
		ev->rotation = 1024;
	else
		ev->rotation = 0;

	if (ev->node[0] < ev->node[2])
		ev->rotation += 2048;
}


// [D] [T]
void InitTrain(EVENT* ev, int count, int type)
{
	int mv;
	int* to;
	int length;
	int height;

	ev->node = &ev->data[3];

	ev->timer = 0;
	ev->flags = ev->data[2] | 0x2;

	if (type == 0)
	{
		height = -1773;
		length = 1600;

		SetElTrainRotation(ev);
	}
	else if (type == 1)
	{
		if(ev->data == VegasMonorailData) // [A]
		{
			length = 1850;
			height = -1100;
		}
		else
		{
			length = 3700;
			height = -734;
		}

		ev->rotation = 0;
	}

	ev->position.vy = height;

	ev->flags &= ~0x7000;

	if (ev->node[3] != PATH_NODE_WRAP && ev->node[3] != PATH_NODE_REVERSE)
		ev->flags |= 0x3000;

	to = ev->node;

	// get direction sign
	if (to[2] == PATH_NODE_STATION)
		mv = to[3] - to[0] >> 0x1f;
	else
		mv = to[2] - to[0] >> 0x1f;

	if (ev->flags & 0x8000)
	{
		ev->position.vz = to[0] + ((count * length ^ mv) - mv);
		ev->position.vx = to[1];
	}
	else
	{
		ev->position.vx = to[0] + ((count * length ^ mv) - mv);
		ev->position.vz = to[1];
	}
}

// [D] [T]
void InitDoor(FixedEvent* ev, EVENT*** e, int* cEvents)
{
	ev->active = 0;
	ev->rotation = ev->finalRotation;
	ev->flags |= 0x200;

	*(FixedEvent**)*e = ev;	// [A] is that gonna work?
	*e = &(**e)->next;

	VisibilityLists(VIS_ADD, (ev - fixedEvent) | 0x80);
}

// [D] [T]
void InitEvents(void)
{
	events.camera = 0;
	events.track = trackingEvent;
	events.cameraEvent = NULL;

	detonator.timer = 0;
	detonator.count = 0;

	trackingEvent[0] = NULL;
	carEvent[0] = NULL;

	cameraEventsActive = 0;
	event_models_active = 0;

	cameraDelay.delay = 0;
	eventHaze = 0;
	carsOnBoat = 0;
	doneFirstHavanaCameraHack = 0;

	boatOffset.vx = 0;
	boatOffset.vy = 0;
	boatOffset.vz = 0;

	VisibilityLists(VIS_INIT, 0);
	TriggerEvent(-1);
}

// [D] [T]
void BoatOffset(SVECTOR* offset, EVENT* ev)
{
	offset->vx = 0;
	offset->vy = -ev->data[2];
	offset->vz = ev->data[4] - ev->position.vz;
}

// [D] [T]
int OnBoat(VECTOR* pos, EVENT* ev, int* dist)
{
	int halfBoatLength;
	int halfBoatWidth;

	if (GameLevel == 1)
	{
		halfBoatWidth = 853;
		halfBoatLength = 2431;
	}
	else
	{
		halfBoatWidth = 1600;
		halfBoatLength = 5376;
	}

	if (ev->position.vx - halfBoatWidth < pos->vx &&
		ev->position.vx + halfBoatWidth > pos->vx)
	{
		*dist = pos->vz - ev->position.vz;

		if (*dist > -halfBoatLength && *dist < halfBoatLength)
		{
			return 1;
		}
	}

	return 0;
}

#ifdef PSX
#define EVENT_RADIUS 4096
#else
#define EVENT_RADIUS 12000
#endif

// [D] [T] [A] long function, please debug more
void SetUpEvents(int full)
{
	int direction;
	int i, n;
	int* p;
	EVENT* evt;

	int detonatorModel;
	int trainModel;

	EVENT** e;

	int cEvents;
	int ElTrackModel;
	int carModel;

	firstEvent = NULL;
	e = &firstEvent;

	// Multiplayer level loaded?
	if (!doSpooling)
	{
		firstEvent = NULL;
		EventCop = NULL;
		return;
	}

	D_MALLOC_BEGIN()

	if (full)
	{
		EventCop = (CELL_OBJECT*)D_MALLOC(sizeof(CELL_OBJECT) * 16);
		event = (EVENT*)mallocptr;
	}

	evt = event;
	cEvents = 0;	// TODO: use D_MALLOC for each event?

	if (GameLevel == 0)
	{
		int count;
		int cBridges;

		cBridges = LiftingBridges[0];

		direction = 0;
		p = &LiftingBridges[1];

		// make lifting bridges
		n = 0;
		while (n < cBridges)
		{
			int timeOffset;
			cameraEventsActive = 1;

			// goose island bridges start/end?
			if (p[0] == 0x100)
			{
				firstMissionEvent = &event[cEvents];
				p++;
			}
			else if (p[0] == 0x8000)
			{
				direction = true;
				p++;
			}

			evt = &event[cEvents];

			evt[0].data = &p[0];
			evt[1].data = &p[2];

			if (direction)
			{
				evt[0].position.vz = (p[0] + p[1]) / 2;
				evt[1].position.vz = (p[1] + p[2]) / 2;
			}
			else
			{
				evt[0].position.vx = (p[0] + p[1]) / 2;
				evt[1].position.vx = (p[1] + p[2]) / 2;
			}

			count = p[3];
			p += 4;

			// randomize the timer of bridges
			timeOffset = (Random2(0) >> (n & 31) & 255) * 32;

			evt = &event[cEvents];
			for (i = 0; i < 2; i++)
			{
				if (direction)
				{
					evt[i].flags = 0x1;
					evt[i].position.vx = *p;
				}
				else
				{
					evt[i].flags = 0x21;
					evt[i].position.vz = *p;
				}

				evt[i].node = p;
				evt[i].radius = EVENT_RADIUS;
				evt[i].timer = timeOffset % 8000;
				evt[i].model = n * 2 + i;
			}

			while (--count >= 0)
			{
				VisibilityLists(VIS_ADD, cEvents | count * 256);
				VisibilityLists(VIS_ADD, cEvents + 1 | count * 256);
				p++;
			}

			*e = &event[cEvents];

			evt = *e;
			evt->next = &event[cEvents + 1];

			cEvents += 2;

			e = &evt->next->next;
			n++;
		}

		if (full)
			ElTrackModel = FindModelIdxWithName("ELTRAIN");

		count = ElTrainData[0];
		p = ElTrainData;

		n = 0;
		missionTrain[0].engine = &event[cEvents];
		missionTrain[1].engine = missionTrain[0].engine;

		// add trains
		while (n < count-1)
		{
			// randomize carriage count
			if (n != 0)
				i = (Random2(0) >> (n & 0x1f) & 3U) + 2;
			else
				i = 5;

			direction = 1;
			n++;
			p++;

			while (--i >= 0)
			{
				evt = &event[cEvents];
				evt->radius = 0;
				evt->data = p;
				evt->position.pad = p - ElTrainData;

				InitTrain(evt, i, 0);

				if (direction)
				{
					direction = 0;
					evt->flags |= 0x400;
				}

				if (full)
					evt->model = ElTrackModel;

				*e = evt;
				e = &(*e)->next;

				VisibilityLists(VIS_ADD, cEvents);

				cEvents++;
			}

			//i = *p;

			do
			{
			} while (*++p + PATH_NODE_WRAP > 1);
		}

		fixedEvent = chicagoDoor;

		InitDoor(&chicagoDoor[0], &e, &cEvents);
		InitDoor(&chicagoDoor[1], &e, &cEvents);

		*e = (EVENT*)&chicagoDoor[2];
		e = &(*e)->next;

		VisibilityLists(VIS_ADD, 130);

		if (full)
		{
			// these models are for ferris wheel
			chicagoDoor[2].initialRotation = FindModelIdxWithName("HUB");
			chicagoDoor[2].minSpeed = FindModelIdxWithName("CAR");
			chicagoDoor[2].model = FindModelIdxWithName("FRAME");
			chicagoDoor[0].model = FindModelIdxWithName(chicagoDoor[0].modelName);
			chicagoDoor[1].model = FindModelIdxWithName(chicagoDoor[1].modelName);

			// Caine's compund semi model
			carModel = FindModelIdxWithName("LORRY");
		}

		// Caine's compound trucks
		multiCar.count = 0;
		multiCar.event = &event[cEvents];

		evt = multiCar.event;
		for (i = 0; i < 7; i++)
		{
			evt[i].flags = 0x680;

			if (full)
				evt[i].model = carModel;

			evt[i].radius = 0;
			evt[i].next = &evt[i + 1];
		}

		cEvents += 7;
	}
	else if (GameLevel == 1)
	{
		evt = event;

		cameraEventsActive = 1;

		evt->radius = 0;
		evt->position.vy = 111;
		evt->position.vx = HavanaFerryData[3];
		evt->position.vz = HavanaFerryData[4];;
		evt->rotation = 3072;
		evt->flags = -0x77ad;
		evt->timer = -1;
		evt->node = HavanaFerryData + 4;
		evt->data = HavanaFerryData;

		// [A] reset Ferry angles
		evt->data[1] = RSIN(CameraCnt * 32) >> 9;
		evt->data[2] = RCOS(CameraCnt * 16) + 4096 >> 7;

		VisibilityLists(VIS_ADD, 0);
		MakeEventTrackable(evt);

		*e = evt;
		fixedEvent = havanaFixed;
		e = &(*e)->next;

		InitDoor(havanaFixed, &e, &cEvents);
		InitDoor(havanaFixed + 2, &e, &cEvents);

		*e = (EVENT*)(&havanaFixed[1]);
		e = &(*e)->next;

		VisibilityLists(VIS_ADD, 129);

		evt[1].flags = 0x4212;
		evt[1].node = HavanaMiniData + 1;
		evt[1].position.vy = 3995;
		evt[1].position.vx = -455167;
		evt[1].position.vz = -125439;
		evt[1].rotation = 0;
		evt[1].timer = -1;
		evt[1].radius = 0;
		evt[1].data = HavanaMiniData;

		VisibilityLists(VIS_ADD, 1);
		*e = event + 1;
		e = &(*e)->next;
		cEvents = 2;

		if (full != 0)
		{
			event->model = FindModelIdxWithName("FERRY");
			event[1].model = FindModelIdxWithName("LIFT");

			havanaFixed[1].model = FindModelIdxWithName(havanaFixed[1].modelName);
			havanaFixed[2].model = FindModelIdxWithName(havanaFixed[2].modelName);
			havanaFixed[0].model = FindModelIdxWithName(havanaFixed[0].modelName);

			foam.model = FindModelPtrWithName("FOAM");
		}
	}
	else if (GameLevel == 2)
	{
		int cCarriages;

		if (full)
			trainModel = FindModelIdxWithName("TRAIN");

		evt = event;

		// in Beat the train we spawn more train cars
		if (gCurrentMissionNumber == 22)
			cCarriages = 9;
		else
			cCarriages = 2;

		for (i = 0; i < cCarriages; i++)
		{
			evt[i].radius = EVENT_RADIUS;

			if (i < 2)
			{
				evt[i].flags = 0x302;
				evt[i].position.vx = VegasParkedTrains[0];
				evt[i].position.vz = VegasParkedTrains[i + 1];
				evt[i].timer = 2;

				VisibilityLists(VIS_ADD, i);
			}

			evt[i].position.vy = -734;
			evt[i].rotation = 0;

			if (full)
				evt[i].model = trainModel;

			*e = &evt[i];
			e = &(*e)->next;
		}

		// zero first and last train links
		evt[cCarriages - 1].next = NULL;
		evt[1].next = NULL;

		cEvents = cCarriages;

		fixedEvent = vegasDoor;
		for (i = 0; i < 5; i++)
		{
			InitDoor(&vegasDoor[i], &e, &cEvents);

			if (full)
				vegasDoor[i].model = FindModelIdxWithName(vegasDoor[i].modelName);
		}

		// [A] Init cut Vegas monorail
		if (full)
			trainModel = FindModelIdxWithName("MONORAIL");

		evt = &event[cEvents];
		
		// [A] add monorail. More than two for some unknown reason are not visible
		cCarriages = 2;

		for (i = 0; i < cCarriages; i++)
		{
			evt[i].radius = EVENT_RADIUS;
			evt[i].position.vx = VegasMonorailData[4];
			evt[i].position.vz = VegasMonorailData[3];
			evt[i].data = VegasMonorailData;

			InitTrain(&evt[i], i, 1);

			VisibilityLists(VIS_ADD, cEvents+i);

			if (full)
				evt[i].model = trainModel;

			*e = &evt[i];
			e = &(*e)->next;
		}

		evt->flags |= 0x500;

		// zero first and last train links
		evt[cCarriages - 1].next = NULL;
		evt[1].next = NULL;

		cEvents += cCarriages;
		evt = &event[cEvents];

		if (gCurrentMissionNumber == 30)
		{
			// Destroy the yard
			evt[0].position.vx = -12283;
			evt[0].position.vy = -275;
			evt[0].position.vz = 841243;

			evt[1].position.vx = -13482;
			evt[1].position.vy = -250;
			evt[1].position.vz = 841184;

			evt[2].position.vx = -14380;
			evt[2].position.vy = -276;
			evt[2].position.vz = 840579;

			i = 0;
			for (i = 0; i < 3; i++)
			{
				if (i == 2)
					evt[i].rotation = 0;
				else
					evt[i].rotation = 2048;

				evt[i].flags = 0x400;
				evt[i].radius = 0;
			}

			if (full)
			{
				detonatorModel = FindModelIdxWithName("DETONATOR");

				evt[0].model = detonatorModel;
				evt[1].model = detonatorModel;
				evt[2].model = detonatorModel;
			}

			firstMissionEvent = &event[cEvents];
			cEvents += 3;
		}
	}
	else if (GameLevel == 3)
	{
		evt = event;

		cameraEventsActive = 1;

		if (full != 0)
			evt->model = FindModelIdxWithName("BOAT01");

		evt->radius = 0;
		evt->position.vy = 256;
		evt->position.vx = RioFerryData[3];
		evt->position.vz = RioFerryData[4];
		evt->flags = -0x77ad;
		evt->rotation = 0;
		evt->timer = -1;
		evt->node = RioFerryData + 4;
		evt->data = RioFerryData;

		VisibilityLists(VIS_ADD, 0);

		MakeEventTrackable(event);
		*e = event;

		cEvents = 1;
		fixedEvent = rioDoor;
		e = &(*e)->next;

		for (i = 0; i < 6; i++)
		{
			InitDoor(&rioDoor[i], &e, &cEvents);

			if (full)
				rioDoor[i].model = FindModelIdxWithName(rioDoor[i].modelName);
		}

		if (full)
		{
			foam.model = FindModelPtrWithName("FOAM");
			detonatorModel = FindModelIdxWithName("DETONATOR");
		}

		evt = event;
		if (gCurrentMissionNumber == 35)
		{
			// setup bombs on Boat Jump
			int dz, dy;

			evt[cEvents].position.vx = 201520;
			evt[cEvents].position.vy = -177;
			evt[cEvents].position.vz = 385248;

			evt[cEvents + 1].position.vx = 201392;
			evt[cEvents + 1].position.vy = -177;
			evt[cEvents + 1].position.vz = 389200;

			evt[cEvents + 2].position.vx = 199376;
			evt[cEvents + 2].position.vy = -177;
			evt[cEvents + 2].position.vz = 389200;

			for (i = 0; i < 3; i++)
			{
				if (i == 2)
					evt[cEvents + i].rotation = 3072;
				else
					evt[cEvents + i].rotation = 1024;

				evt[cEvents + i].flags = 0x80;
				evt[cEvents + i].radius = 0;
				evt[cEvents + i].data = &evt->data[1];

				dz = evt->position.vz - evt[cEvents + i].position.vz;
				dy = evt->position.vy - evt[cEvents + i].position.vy;

				evt[cEvents + i].node = (int*)ABS(SquareRoot0(dz * dz + dy * dy));
			}

			if (full != 0)
			{
				evt[cEvents].model = detonatorModel;
				evt[cEvents + 1].model = detonatorModel;
				evt[cEvents + 2].model = detonatorModel;
			}

			firstMissionEvent = &event[cEvents];
			cEvents += 3;
		}
		else if (gCurrentMissionNumber - 39U < 2)
		{
			// setup Lenny helicopter
			evt[cEvents].flags = 0xC0;

			if (gCurrentMissionNumber == 39)
			{
				evt[cEvents].position.vx = 65223;
				evt[cEvents].position.vy = -774;
				evt[cEvents].position.vz = -518866;
				evt[cEvents].rotation = 2624;
				evt[cEvents].timer = -1;

				HelicopterData.pitch = 0;
				HelicopterData.roll = 0;
			}
			else
			{
				evt[cEvents].position.vy = -1000;
				evt[cEvents].rotation = -1;
				evt[cEvents].timer = 0;
				evt[cEvents].flags |= 0x100;
			}

			HelicopterData.rotorrot = Random2(0) & 0xff;
			HelicopterData.rotorvel = 1;

			evt[cEvents].node = (int*)&MissionTargets[4];
			evt[cEvents].radius = 0;

			if (full)
			{
				HelicopterData.cleanModel = FindModelIdxWithName("CHOPPERCLEAN");
				HelicopterData.deadModel = FindModelIdxWithName("CHOPPERDAM");
				GetTextureDetails("ROTOR", &HelicopterData.rotorTexture);
			}

			evt[cEvents].model = HelicopterData.cleanModel;
			VisibilityLists(VIS_ADD, cEvents);
			MakeEventTrackable(event + cEvents);

			*e = &evt[cEvents++];

			e = &(*e)->next;
		}
	}

	*e = NULL;

	if (full)
		mallocptr += cEvents * sizeof(EVENT);

	D_MALLOC_END();
}

// [D] [T]
void InitEventCamera(void)
{
	eventCamera.position = camera_position;
	eventCamera.yAng = camera_angle.vy;
	eventCamera.matrix = inv_camera_matrix;
	eventCamera.rotate = 1;
	events.camera = 1;
}

// [D] [T]
void ResetEventCamera(void)
{
	camera_position = eventCamera.position;
	camera_angle.vy = eventCamera.yAng;
	inv_camera_matrix = eventCamera.matrix;
	events.camera = 0;
}

// [D] [T]
void SetCamera(EVENT* ev)
{
	int iPivot;
	int axis;
	int rotation;
	VECTOR pivot;
	SVECTOR offset;
	MATRIX matrix;
	SVECTOR temp;

	offset.vx = 0;
	offset.vy = 0;
	offset.vz = 0;

	if (eventCamera.rotate != 0)
		inv_camera_matrix = eventCamera.matrix;

	pivot.vy = 0;

	if (ev->flags & 0x800)
	{
		iPivot = ev->position.vz;
		rotation = ev->data[1];

		offset = boatOffset;

		if (GameLevel == 1)
			pivot.vy = 111;
		else
			pivot.vy = 256;

		axis = 0;
	}
	else
	{
		axis = ev->flags & 0x30;

		rotation = ev->rotation;
		iPivot = *ev->data;
	}

	if (axis == 0)
	{
		rotation = -rotation;

		pivot.vx = camera_position.vx;
		pivot.vz = iPivot;
	}
	else
	{
		pivot.vz = camera_position.vz;
		pivot.vx = iPivot;
	}

	if (rotation == 0)
	{
		camera_position.vx = eventCamera.position.vx + offset.vx;
		camera_position.vy = eventCamera.position.vy + offset.vy;
		camera_position.vz = eventCamera.position.vz + offset.vz;
		camera_angle.vy = eventCamera.yAng;
	}
	else
	{
		camera_position = eventCamera.position;

		InitMatrix(matrix);

		if (axis == 0x10)
		{
			camera_angle.vy = camera_angle.vy - rotation;
			_RotMatrixY(&matrix, rotation);
		}
		else if (axis == 0)
		{
			_RotMatrixX(&matrix, rotation);
		}
		else if (axis == 0x20)
		{
			_RotMatrixZ(&matrix, rotation);
		}

		temp.vx = camera_position.vx - pivot.vx;
		temp.vy = camera_position.vy - pivot.vy;
		temp.vz = camera_position.vz - pivot.vz;

		gte_SetRotMatrix(&matrix);
		gte_SetTransVector(&pivot);

		gte_ldv0(&temp);
		gte_rtv0tr();
		gte_stlvnl(&camera_position);

		// this seems to reflect matrix by Y axis
		short v1, v0, a1, a2;

		v1 = matrix.m[0][1];
		v0 = matrix.m[1][0];
		a1 = matrix.m[0][2];
		a2 = matrix.m[2][0];

		v1 = v1 ^ v0;
		v0 = v0 ^ v1;
		v1 = v1 ^ v0;
		matrix.m[1][0] = v0;
		matrix.m[0][1] = v1;

		a1 = a1 ^ a2;
		a2 = a2 ^ a1;
		a1 = a1 ^ a2;
		matrix.m[2][0] = a2;
		matrix.m[0][2] = a1;

		v0 = matrix.m[2][1];
		v1 = matrix.m[1][2];
		v1 = v1 ^ v0;
		v0 = v0 ^ v1;
		v1 = v1 ^ v0;
		matrix.m[2][1] = v0;
		matrix.m[1][2] = v1;

		// OLD
		//gte_SetRotMatrix(&inv_camera_matrix);
		//MulRotMatrix(&matrix);
		//inv_camera_matrix = matrix;

		// NEW
		gte_MulMatrix0(&inv_camera_matrix, &matrix, &inv_camera_matrix);

		camera_position.vx += offset.vx;
		camera_position.vy += offset.vy;
		camera_position.vz += offset.vz;
	}

	SetCameraVector();

	if (rotation != 0 || eventCamera.rotate != 0)
	{
		Set_Inv_CameraMatrix();
		SetCameraVector();
		SetupDrawMapPSX();
	}

	eventCamera.rotate = rotation;

	if (ev->flags & 0x800)
	{
		ev->flags &= ~0x1;
		events.draw = 0;
	}
	else
	{
		events.draw = ev->model;
	}
}

// [D] [T]
void EventCollisions(CAR_DATA* cp, int type)
{
	if (carsOnBoat >> CAR_INDEX(cp) == 0)
		return;

	if (type == 0)
	{
		events.draw = 0;
		events.camera = 1;

		cp->hd.where.t[0] += boatOffset.vx;
		cp->hd.where.t[1] += boatOffset.vy;
		cp->hd.where.t[2] += boatOffset.vz;

		return;
	}

	cp->hd.where.t[0] -= boatOffset.vx;
	cp->hd.where.t[1] -= boatOffset.vy;
	cp->hd.where.t[2] -= boatOffset.vz;

	events.camera = 0;
}

// [D] [T]
void NextNode(EVENT* ev)
{
	if (ev->node[2] == PATH_NODE_STATION || ev->node[2] == PATH_NODE_REVERSE)
	{
		ev->node = &ev->node[2];
	}
	else
	{
		ev->node = &ev->node[1];
		ev->flags ^= 0x8000;
	}

	if (*ev->node == PATH_NODE_CYCLE)
	{
		ev->node = &ev->data[3];
	}
	else if (ev->node[3] == PATH_NODE_WRAP || ev->node[3] == PATH_NODE_REVERSE)
	{
		ev->flags &= ~0x7000;
		return;
	}

	ev->flags &= ~0x7000;
	ev->flags |= 0x3000;

	SetElTrainRotation(ev);
}

// [D] [T]
void StepFromToEvent(EVENT* ev)
{
	int df;
	int md;
	long* curr;
	int* to;
	int direction;
	int d, d2;

	if (ev->timer > 0)
	{
		ev->timer--;
		return;
	}

	if (ev->timer != 0)
		return;

	direction = ev->flags & 0xc000;
	to = &ev->node[1];

	if (direction == 16384)
	{
		curr = &ev->position.vy;
	}
	else if (direction <= 16384)
	{
		if (direction != 0)
			curr = &ev->position.vx;
		else
			curr = &ev->position.vz;
	}
	else if (direction == 32768)
	{
		curr = &ev->position.vz;
	}

	d = *curr - *ev->node;
	d2 = *to - *curr;

	if (ABS(d2) < ABS(d))
		md = ABS(d2);
	else
		md = ABS(d);

	df = *to - ev->node[0] >> 31;

	// movement speed
	if ((ev->flags & 0xC000) == 0x4000)
	{
		if (md > 1024)
			md = ev->data[0] ^ df;
		else
			md = ((ev->data[0] - 1) * RSIN(md)) / 4096 + 1 ^ df;
	}
	else
	{
		if (md > 2048)
			md = ev->data[0] ^ df;
		else
			md = ((ev->data[0] - 1) * RSIN(md >> 1)) / 4096 + 1U ^ df;
	}

	*curr += (md - df);

	if ((d2 ^ *to - *curr) < 0)
	{
		*curr = *to;

		ev->timer = -1;

		if (ev == events.cameraEvent)
		{
			SetSpecialCamera(SPECIAL_CAMERA_RESET, 0);

			if(direction == 16384)
			{
				SetMSoundVar(3, NULL);
			}
		}
	}
}


// [D] [T]
void StepPathEvent(EVENT* ev)
{
	static int speed;

	int d1;
	int* i;
	int d;
	int* from;
	int* to;
	Station station;
	int direction;
	long* curr;
	int dir;
	int turn[4];
	XZPAIR centre;
	XZPAIR offset;
	short flags;

	if (ev->timer != 0)
	{
		if (ev->timer == 1)
		{
			ev->timer--;

			do
			{
				if(!(ev->data[2] & 0x10000))
					NextNode(ev);
				
				flags = ev->flags;

				ev = ev->next;

				if (ev == NULL)
				{
					speed = 0;
					return;
				}

			} while (ev->flags == (flags & ~0x400));
		}
		else
		{
			if (!(ev->flags & 0x100))
				ev->timer--;
		}

		speed = 0;
		return;
	}

	flags = ev->flags;

	if (speed == 0 && (flags & 0x400) == 0)
		return;

	//
	// [A] completely custom code for Luxor Monorail because OG code for handling trains SUCKS!!!
	if(ev->data[2] & 0x10000)
	{
		from = ev->node;
		to = &from[2];
		
		if (flags & 0x8000)
			curr = &ev->position.vz;
		else
			curr = &ev->position.vx;

		i = &from[0];
		d = *to - *curr;

		if (d < 0)
			dir = -1;
		else
			dir = 1;

		d = ABS(d);
		d1 = ABS(*curr - *i);

		if (d1 > d)
			d1 = d;

		d = ABS(*curr - *i);

		if (d <= d1)
			d1 = d;

		if (ev->flags & 0x400)
			speed = ev->data[1];

		if ((d1 < 6000 && d1 < 8048) && (ev->flags & 0x400))
		{
			speed = ev->data[0] + (speed - ev->data[0]) * RSIN(((d1 - 2048) * 1024) / 8048) / 4096;
		}

		*curr += speed * dir;

		int dist = (*to - *curr) * dir;

		if(dist < (-dir)*2048 && ev->flags & 0x400)
		{
			EVENT* _evit = ev;

			do
			{
				_evit->flags &= ~0x100;
				_evit->timer = 300;

				// manually advance node
				do
				{
					_evit->node += 1;

					if (_evit->node[2] == PATH_NODE_WRAP)
					{
						// reinitialize if we reached WRAP
						_evit->node = &ev->data[3];
						break;
					}
				} while (_evit->node[2] == PATH_NODE_WRAP || _evit->node[2] == PATH_NODE_REVERSE);

				_evit = _evit->next;
			} while (_evit);
		
		}

		return;
	}
	
	from = ev->node;
	to = &from[2];
	
	if (*from == PATH_NODE_STATION)
	{
		station = EVENT_LEAVING;
		i = &from[1];
	}
	else
	{
		station = EVENT_APPROACHING;

		i = &from[0];

		if (from[2] == PATH_NODE_STATION)
		{
			to = &from[3];
		}
		else
		{
			station = EVENT_NO_STATION;

			if (from[-1] == PATH_NODE_STATION)
				i = &from[-2];
			else if (from[2] == PATH_NODE_CYCLE)
				to = &ev->data[3];
			else if (from[1] == PATH_NODE_CYCLE && &ev->data[3] < to)
				to = &ev->data[4];
		}
	}

	// move train
	if ((flags & 0x7000) == 0x1000)
	{
		u_int loop; // unsigned on purspose

		if (flags & 0x8000)
		{
			direction = -1;
			loop = 3;
		}
		else
		{
			direction = 1;
			loop = 0;
		}

		if (from[0] == PATH_NODE_STATION)
			i = &from[-2];

		while (loop < 4)
		{
			if (*i == PATH_NODE_CYCLE)
				i = &ev->data[3];
			else if (*i == PATH_NODE_STATION)
				i += 2;

			turn[loop] = *i;
			i++;
			loop += direction;
		}

		if (turn[0] - turn[2] > -1)
			centre.x = turn[2] + 2048;
		else
			centre.x = turn[2] - 2048;

		if (turn[3] - turn[1] > -1)
			centre.z = turn[1] + 2048;
		else
			centre.z = turn[1] - 2048;

		if (ev->flags & 0x400)
			speed = ev->data[0];

		offset.x = (ev->position.vz - centre.z) * speed / 2048;
		offset.z = (centre.x - ev->position.vx) * speed / 2048;

		if (((turn[2] - turn[0]) * offset.x + (turn[3] - turn[1]) * offset.z) * direction < 0)
		{
			offset.x = -offset.x;
			offset.z = -offset.z;
		}

		ev->position.vx += offset.x;
		ev->position.vz += offset.z;

		ev->rotation = ratan2(offset.x, offset.z) + 1024U & 0xfff;

		if (ev->flags & 0x8000)
		{
			centre.x = ev->position.vx - centre.x;

			if (turn[0] - turn[2] < 0)
			{
				if (centre.x > -1)
					return;
			}
			else
			{
				if (centre.x < 1)
					return;
			}

			ev->position.vz = turn[1];

			NextNode(ev);
		}
		else
		{
			centre.z = ev->position.vz - centre.z;

			if (turn[3] - turn[1] < 0)
			{
				if (centre.z > -1)
					return;
			}
			else
			{
				if (centre.z < 1)
					return;
			}

			ev->position.vx = turn[2];
			NextNode(ev);
		}

		return;
	}

	if (flags & 0x8000)
		curr = &ev->position.vz;
	else
		curr = &ev->position.vx;

	d = *to - *curr;

	if (d < 0)
		dir = -1;
	else
		dir = 1;

	d = ABS(d);
	d1 = ABS(*curr - *i);

	if (d1 > d)
	{
		if (station == EVENT_LEAVING)
			station = EVENT_NO_STATION;

		d1 = d;
	}
	else
	{
		if (station == EVENT_APPROACHING)
			station = EVENT_NO_STATION;
	}

	d = ABS(*curr - *i);

	if (d <= d1)
		d1 = d;

	if (ev->flags & 0x400)
	{
		if (ev->flags & 0x80)
		{
			if (CameraCnt < 0x1000)
				speed = (ev->data[1] * (4096 - CameraCnt) + ev->data[2] * CameraCnt) / 4096;
			else
				speed = ev->data[2];
			//debugSpeed = speed;
		}

		speed = ev->data[1];
	}

	if ((d1 < 6000 || station == EVENT_NO_STATION && d1 < 8048) && (ev->flags & 0x400))
	{
		if (station == EVENT_NO_STATION)
		{
			if ((ev->flags & 0x7000U) == 0x3000)
			{
				speed = ev->data[0] + (speed - ev->data[0]) * RSIN(((d1 - 2048) * 1024) / 8048) / 4096;
			}
		}
		else
		{
			// acceleration or slowdown
			speed = 5 + (speed - 5) * RSIN((d1 << 10) / 6000) / 4096;
		}
	}

	*curr += speed * dir;

	int dist = (*to - *curr) * dir;
	
	if (station == EVENT_NO_STATION && (ev->flags & 0x7000))
	{
		if (dist < 2048)
		{
			if ((ev->flags & 0x7000) == 0x3000)
			{
				ev->flags &= ~0x7000;
				ev->flags |= 0x1000;
			}
		}
		
		return;
	}

	if (dist < 0)
	{
		if (station == EVENT_NO_STATION)
		{
			// [A] preserve direction flag or train will get stuck
			// i might have been decompiled it wrong but now it works
			dir = ev->flags & 0x400;
			
			InitTrain(ev, 0, GameLevel == 0 ? 0 : 1);

			if (dir)
				ev->flags |= 0x400;
		}
		else if (ev->flags & 0x400)
		{
			ev->timer = 300;
		}
	}
}


// [D] [T]
int GetBridgeRotation(int timer)
{
	if (_CutRec_IsOn())
		return 0;

	if (gDisableChicagoBridges)
		return 0;

	if (timer > 2600)
		return 0;

	if (timer > 1600)
		timer = 2600 - timer;
	else if (timer > 1000)
		timer = 1000;

	return (4096 - RCOS((timer * 2048) / 1000)) * 800 / 8192;
}

// [D] [T]
void StepHelicopter(EVENT* ev)
{
	static int rotating = 1;

	short sign;
	int rot;
	int pitch, roll, d2p, d2r;
	int direction;
	int vx, vz;
	XZPAIR vel;
	VECTOR pos;
	VECTOR drift = { 2,3,2 };

	if (ev->timer >= 0)
	{
		int b, c, d, t, tt1, tt2;
		t = ev->timer >> 3;

		tt1 = FIXEDH(t * t);
		tt2 = FIXEDH(tt1 * t);

		b = (tt2 * 3 - tt1 * 5) / 2 + 4096;
		c = tt1 * 2 + (t - tt2 * 3) / 2;
		d = (tt2 - tt1) / 2;

		ev->position.vx = ev->node[0] + FIXEDH((ev->node[2] - ev->node[0]) * b + (ev->node[4] - ev->node[0]) * c + (ev->node[6] - ev->node[0]) * d);
		ev->position.vz = ev->node[1] + FIXEDH((ev->node[3] - ev->node[1]) * b + (ev->node[5] - ev->node[1]) * c + (ev->node[7] - ev->node[1]) * d);

		ev->timer += HelicopterData.speed;

		if (ev->timer < 0)
		{
			if (ev->node[9] == (ev->node[8] == 0))
			{
				ev->timer = -1;
			}
			else
			{
				ev->timer = 0;
				ev->node += 2;
			}
		}

		if (ev->rotation == -1)
		{
			vel.x = (ev->node[4] - ev->node[0]) / 2;
			vel.z = (ev->node[5] - ev->node[1]) / 2;

			ev->rotation = ratan2(vel.x, vel.z);

			HelicopterData.pitch = 0;
			HelicopterData.roll = 0;
			HelicopterData.dp = 0;
			HelicopterData.dr = 0;
		}
		else
		{
			vel.x = ev->position.vx - HelicopterData.lastX;
			vel.z = ev->position.vz - HelicopterData.lastZ;
		}

		rot = ev->rotation;

		vz = vel.z * RCOS(rot) + vel.x * RSIN(rot);
		vx = vel.x * RCOS(rot) - vel.z * RSIN(rot);

		pitch = HelicopterData.pitch;

		if (ABS(vz) <= 900000)
			pitch = -HelicopterData.pitch - (RSIN((vz * 1024) / 900000) >> 3);
		else if (vz < 1)
			pitch = 512 - pitch;
		else
			pitch = -512 - pitch;

		roll = HelicopterData.roll;

		if (ABS(vx) <= 150000)
			roll = (RSIN((vx * 1024) / 150000) >> 3) - roll;
		else if (vx < 1)
			roll = -512 - roll;
		else
			roll = 512 - roll;

		d2p = pitch - HelicopterData.dp;

		if (ABS(d2p) < 5)
		{
			HelicopterData.dp = pitch;
		}
		else
		{
			sign = (d2p >> 0x1f);
			HelicopterData.dp += (sign ^ 5) - sign;
		}

		d2r = roll - HelicopterData.dr;

		if (ABS(d2r) < 5)
		{
			HelicopterData.dr = roll;
		}
		else
		{
			sign = (d2r >> 0x1f);
			HelicopterData.dr += (sign ^ 5) - sign;
		}

		HelicopterData.pitch = DIFF_ANGLES(-HelicopterData.dp, HelicopterData.pitch);// (HelicopterData.pitch + HelicopterData.dp + 2048U & 0xfff) - 2048;
		HelicopterData.roll = DIFF_ANGLES(-HelicopterData.dr, HelicopterData.roll); //(HelicopterData.roll + HelicopterData.dr + 2048U & 0xfff) - 2048;

		rot = DIFF_ANGLES(ev->rotation, ratan2(vel.x, vel.z)); //(ratan2(vel.x, vel.z) - ev->rotation + 2048U & 0xfff) - 2048;

		if (ABS(rot) > 512)
		{
			if (rot > 0)
				direction = 1024;
			else
				direction = -1024;
		}
		else
		{
			direction = RSIN(rot * 2) >> 2;
		}

		ev->rotation += FIXEDH(FIXEDH(direction * direction) * direction);
		ev->rotation &= 0xfff;

		if (GetSurfaceIndex(&ev->position) == -23)
		{
			if (ev->position.vy < -50)
			{
				ev->position.vy += 10;
			}
			else
			{
				pos.vy = 0;
				pos.vx = ev->position.vx;
				pos.vz = ev->position.vz;

				Setup_Smoke(&pos, 100, 500, SMOKE_WHITE, 0, &dummy, 0);
			}
		}
		else
		{
			if (ev->position.vy > -1000)
				ev->position.vy -= 10;
		}

		HelicopterData.lastX = ev->position.vx;
		HelicopterData.lastZ = ev->position.vz;

		SetMSoundVar((intptr_t)&ev->position, NULL);

		if ((ev->flags & 0x100) && (Random2(0) & 3) == (CameraCnt & 3U))
		{
			Setup_Smoke(&ev->position, 100, 500, SMOKE_BLACK, 0, &dummy, 0);
		}
	}

	if (ev->model == HelicopterData.deadModel)
	{
		pos.vy = -200;
		pos.vx = ev->position.vx + (rand() & 0xff) - 128;
		pos.vz = ev->position.vz + (rand() & 0xff) - 128;

		Setup_Smoke(&pos, 50, 100, SMOKE_FIRE, 0, &dummy, 0);
		Setup_Smoke(&pos, 100, 500, SMOKE_BLACK, 0, &drift, 0);

		SetMSoundVar(0, NULL);
	}
	else
	{
		HelicopterData.rotorrot += HelicopterData.rotorvel;
		HelicopterData.rotorvel += rotating;

		if (ABS(HelicopterData.rotorvel) > 256)
			rotating = -rotating;
	}
}

// [D] [T]
void StepEvents(void)
{
	EVENT* ev;
	int i;
	VECTOR old;
	XZPAIR speed;
	VECTOR* pos;
	EVENT* evt;
	VECTOR* vel;
	CELL_OBJECT* cop;
	CAR_DATA* cp;
	int onBoatLastFrame;
	int dist;
	int thisCamera, otherCamera;

	onBoatLastFrame = carsOnBoat;
	ev = firstEvent;

	if (detonator.timer)
		DetonatorTimer();

	while (ev)
	{
		//carsOnBoat = onBoatLastFrame;

		if (ev->flags & 2)
		{
			if (ev->flags & 0x40)
			{
				i = 0;
				cp = car_data;

				carsOnBoat = 0;
				do {

					if (cp->controlType != CONTROL_TYPE_NONE &&
						OnBoat((VECTOR*)cp->hd.where.t, ev, &dist))
					{
						carsOnBoat |= 1 << i;
					}

					i++;
					cp++;
				} while (i < MAX_CARS && i < 32);

				// make Tanner on boat also
				if (player[0].playerType == 2 && OnBoat((VECTOR*)player[0].pos, ev, &dist))
					carsOnBoat |= (1 << TANNER_COLLIDER_CARID) | 0x200000;// 0x300000;

				BoatOffset(&boatOffset, ev);

				old.vx = ev->position.vx;
				old.vz = ev->position.vz;
			}

			if (ev->flags & 0x10)
				StepFromToEvent(ev);
			else
				StepPathEvent(ev);

			if (ev->flags & 0x800)
			{
				ev->data[1] = RSIN(CameraCnt * 32) >> 9;
				ev->data[2] = RCOS(CameraCnt * 16) + 4096 >> 7;

				if (detonator.timer - 1U < 159) // HMM?
				{
					int tmSqr;
					tmSqr = detonator.timer * detonator.timer;

					ev->data[1] -= RSIN(detonator.timer * 64) * tmSqr >> 18; //rcossin_tbl[(detonator.timer & 0x3fU) * 128] * tmSqr >> 0x12;
					ev->data[2] -= RSIN(detonator.timer * 64) * tmSqr >> 16; //rcossin_tbl[(detonator.timer & 0x3fU) * 128] * tmSqr >> 0x10;
				}

				if (foam.rotate & 0xffff7fffU) // HMMMMMM?
					foam.rotate--;
				else
					foam.rotate ^= ((Random2(0) & 0xf00) >> 8) + 8U | 0x8000;
			}

			// move cars on boats
			if ((ev->flags & 0x40) && (carsOnBoat != 0 || onBoatLastFrame != 0))
			{
				int bit;

				speed.x = ev->position.vx - old.vx;
				speed.z = ev->position.vz - old.vz;

				// go thru cars
				for (i = 0; i < MAX_CARS + 1 && i < 32; i++)
				{
					bit = (1 << i);

					if (i == TANNER_COLLIDER_CARID)
					{
						pos = (VECTOR*)player[0].pos;
						vel = NULL;
					}
					else
					{
						pos = (VECTOR*)car_data[i].hd.where.t;
						vel = (VECTOR*)car_data[i].st.n.linearVelocity;
					}

					// update position and add velocity
					if (carsOnBoat & bit)
					{
						pos->vx += speed.x;
						pos->vz += speed.z;

						if (i == TANNER_COLLIDER_CARID)
						{
							SetTannerPosition(pos);
							carsOnBoat &= ~(1 << TANNER_COLLIDER_CARID);
						}
						else if ((onBoatLastFrame & bit) == 0)
						{
							vel->vx -= speed.x * 4096;
							vel->vz -= speed.z * 4096;
						}

						// [A] this causes drifting, StepOneCar already does better job at updating this
						// car_data[i].st.n.fposition[0] = pos->vx << 4;
						// car_data[i].st.n.fposition[2] = pos->vz << 4;
					}
					else if (vel && (onBoatLastFrame & bit))
					{
						vel->vx += speed.x * 4096;
						vel->vz += speed.z * 4096;
					}
				}
			}
		}
		else
		{
			ushort flags;
			flags = ev->flags & 0xcc0;

			if (flags == 0x40)
			{
				// perform rotation of doors
				if (ev->timer != 0)
				{
					FixedEvent* door;
					int sign, rotAngle;
					unsigned short* target;

					door = (FixedEvent*)ev;

					if (door->active == 1)
						target = &door->finalRotation;
					else
						target = &door->initialRotation;

					rotAngle = ABS(door->rotation - door->initialRotation) * 2048 / ABS(door->finalRotation - door->initialRotation);

					sign = (*target - door->rotation) >> 0x1f;
					door->rotation += (door->minSpeed + (RSIN(rotAngle) * (door->maxSpeed - door->minSpeed) >> 0xc) ^ sign) - sign;

					// check if complete
					if (((*target - door->rotation) ^ sign) - sign < 0)
					{
						door->rotation = *target;
						door->active = 0;

						if (gCurrentMissionNumber != 30)
							SetMSoundVar(3, NULL);

						if (door == (FixedEvent*)events.cameraEvent)
							SetSpecialCamera(SPECIAL_CAMERA_RESET, 0);
					}
				}
			}
			else if (flags < 0x41)
			{
				// perform bridge rotation
				if ((ev->flags & 0xcc0) == 0)
				{
					ev->rotation = GetBridgeRotation(ev->timer);

					if (ev->model & 1)
						ev->rotation = -ev->rotation;

					if ((ev->flags & 0x100) == 0 || ev->timer <= 1000)
					{
						ev->timer++;
						ev->timer %= 8000;
					}
				}
			}
			else if (flags == 0xc0)
			{
				StepHelicopter(ev);
			}
			else if (flags == 0x440)
			{
				// rotate ferris wheel
				if ((CameraCnt & 0x1fU) == 0)
				{
					if ((chicagoDoor[2].active & 1U) == 0)
					{
						chicagoDoor[2].active += 2;

						if (chicagoDoor[2].active == 30)
							chicagoDoor[2].active = 31;

					}
					else
					{
						chicagoDoor[2].active -= 2;

						if (chicagoDoor[2].active == -1)
							chicagoDoor[2].active = 0;
					}
				}

				chicagoDoor[2].rotation -= chicagoDoor[2].active;
				chicagoDoor[2].rotation &= 0xfff;
			}
		}

		onBoatLastFrame = carsOnBoat;

		ev = ev->next;
	}

	VisibilityLists(VIS_SORT, 0);

	if (EventCop != NULL)
	{
		event_models_active = 0;

		for (i = 0; i < NumPlayers; i++)
		{
			unsigned short* x;
			unsigned short* z;

			if (i == 0)
			{
				thisCamera = 0x8000;
				otherCamera = 0x4000;

			}
			else
			{
				thisCamera = 0x4000;
				otherCamera = 0x8000;
			}

			VisibilityLists(VIS_NEXT, i);

			x = xVis;
			while ((*x & thisCamera) == 0)
			{
				if ((*x & otherCamera) == 0)
				{
					if ((*x & 0x80) == 0)
						evt = &event[*x & 0x7f];
					else
						evt = (EVENT*)&fixedEvent[*x & 0x7f];

					// events that enable drawing
					if ((evt->flags & 0x204) == 0x200)
					{
						z = zVis;

						while ((*z & thisCamera) == 0)
						{
							if ((*z & otherCamera) == 0 && (*x & 0xfff) == (*z & 0xfff))
							{
								cop = &EventCop[event_models_active++];

								cop->pos.vx = evt->position.vx;
								cop->pos.vy = evt->position.vy;
								cop->pos.vz = evt->position.vz;
								cop->yang = (evt->rotation >> 6);
								cop->type = evt->model;

								// [A] train should be only dangerous in "Beat the train"
								if ((evt->flags & 0x12) == 2 && evt->data == VegasTrainData)
									cop->pad = 1;
								else
									cop->pad = 0;

								evt->flags |= 0x4;
							}
							z++;
						}
					}
				}

				x++;
			}
		}
	}

	if (cameraDelay.delay != 0 && --cameraDelay.delay == 0)
	{
		SetSpecialCamera((SpecialCamera)cameraDelay.type, 1);
	}
}

// [D] [T]
void DrawFerrisWheel(MATRIX* matrix, VECTOR* pos)
{
	MODEL* model;
	int loop;
	int sn, cs, angle;
	int rotation;
	VECTOR offset;
	VECTOR carPos;

	if (chicagoDoor[2].model == -1)
		return;

	RenderModel(modelpointers[chicagoDoor[2].model], matrix, pos, 0, PLOT_NO_CULL, 1, 0);

	if (chicagoDoor[2].model != -1)
		RenderModel(modelpointers[chicagoDoor[2].initialRotation], NULL, NULL, 0, 0, 1, 0);

	matrix->m[0][0] = -matrix->m[0][0];
	matrix->m[1][0] = -matrix->m[1][0];
	matrix->m[2][0] = -matrix->m[2][0];

	RenderModel(modelpointers[chicagoDoor[2].model], matrix, pos, 0, PLOT_NO_CULL, 1, 0);

	if (chicagoDoor[2].minSpeed == -1)
		return;

	VECTOR spoke[2] = {
		{0, 0, 2677},
		{0, 2677, 0}
	};

	rotation = 0;

	model = modelpointers[chicagoDoor[2].minSpeed];

	SetRotMatrix(&inv_camera_matrix);
	_MatrixRotate(&spoke[0]);
	_MatrixRotate(&spoke[1]);

	for (loop = 0; loop < 5; loop++)
	{
		angle = chicagoDoor[2].rotation + rotation;

		sn = RSIN(angle);
		cs = RCOS(angle);

		offset.vx = FIXEDH(spoke[0].vx * sn + spoke[1].vx * cs);
		offset.vy = FIXEDH(spoke[0].vy * sn + spoke[1].vy * cs);
		offset.vz = FIXEDH(spoke[0].vz * sn + spoke[1].vz * cs);

		carPos.vx = pos->vx + offset.vx;
		carPos.vy = pos->vy + offset.vy;
		carPos.vz = pos->vz + offset.vz;
		gte_SetTransVector(&carPos);

		RenderModel(model, NULL, NULL, 0, 0, 1, 0);

		carPos.vx = pos->vx - offset.vx;
		carPos.vy = pos->vy - offset.vy;
		carPos.vz = pos->vz - offset.vz;
		gte_SetTransVector(&carPos);

		RenderModel(model, NULL, NULL, 0, 0, 1, 0);
		rotation += 410;
	}
}

// [D] [T]
void DrawRotor(VECTOR pos, MATRIX* matrix)
{
	POLY_FT4* poly;
	MATRIX localMat;

	int z;
	SVECTOR v[5] = {
		{0,-470,-120},
		{1024,0,0 },
		{0,0,1024 },
		{0,0,0},
		{0,0,0},
	};

	InitMatrix(localMat);

	pos.vx -= camera_position.vx;
	pos.vy -= camera_position.vy;
	pos.vz -= camera_position.vz;

	_RotMatrixY(&localMat, HelicopterData.rotorrot & 0xfff);
	ApplyMatrixSV(matrix, v, v);
	MulMatrix0(matrix, &localMat, &localMat);
	ApplyMatrixSV(&localMat, v + 1, v + 1);
	ApplyMatrixSV(&localMat, v + 2, v + 2);

	v[0].vx += pos.vx;
	v[0].vy += pos.vy;
	v[0].vz += pos.vz;

	v[1].vx += v[0].vx;
	v[1].vy += v[0].vy;
	v[1].vz += v[0].vz;

	v[2].vx += v[0].vx;
	v[2].vy += v[0].vy;
	v[2].vz += v[0].vz;

	v[4].vx = v[0].vx - v[2].vx;
	v[4].vy = v[0].vy - v[2].vy;
	v[4].vz = v[0].vz - v[2].vz;

	v[3].vx = v[0].vx + v[4].vx;
	v[3].vy = v[0].vy + v[4].vy;
	v[3].vz = v[0].vz + v[4].vz;


	gte_SetRotMatrix(&inv_camera_matrix);
	gte_SetTransVector(&dummy);

	gte_ldv3(&v[0], &v[1], &v[2]);

	gte_rtpt();

	poly = (POLY_FT4*)current->primptr;

	*(ushort*)&poly->u0 = *(ushort*)&HelicopterData.rotorTexture.coords.u0;
	*(ushort*)&poly->u1 = *(ushort*)&HelicopterData.rotorTexture.coords.u1;
	*(ushort*)&poly->u2 = ((*(ushort*)&HelicopterData.rotorTexture.coords.u0 & 0xfefe) >> 1) + ((*(ushort*)&HelicopterData.rotorTexture.coords.u3 & 0xfefe) >> 1);
	*(ushort*)&poly->u3 = *(ushort*)&HelicopterData.rotorTexture.coords.u3;

	poly->tpage = HelicopterData.rotorTexture.tpageid | 0x40;
	poly->clut = HelicopterData.rotorTexture.clutid;

	setPolyFT4(poly);
	setSemiTrans(poly, 1);

	poly->r0 = 127;
	poly->g0 = 127;
	poly->b0 = 127;

	gte_avsz3();

	gte_stsxy3(&poly->x2, &poly->x1, &poly->x0);

	gte_stsz(&z);
	z = z + 1000 >> 3;

	gte_ldv0(&v[3]);

	gte_rtps();

	gte_stsxy(&poly->x3);

	addPrim(current->ot + z, poly);

	current->primptr += sizeof(POLY_FT4);

	*(poly + 1) = *poly;
	poly++;

	v[3].vz = v[0].vz * 2 - v[1].vz;
	v[3].vy = v[0].vy * 2 - v[1].vy;
	v[3].vx = v[0].vx * 2 - v[1].vx;

	gte_ldv0(&v[3]);

	gte_rtps();

	gte_stsxy(&poly->x1);
	addPrim(current->ot + z, poly);

	*(ushort*)&poly->u1 = *(ushort*)&HelicopterData.rotorTexture.coords.u2;

	current->primptr += sizeof(POLY_FT4);
}

// [D] [T]
void DrawEvents(int camera)
{
	static EVENT* nearestTrain; // offset 0x28
	static int distanceFromNearestTrain; // offset 0x2c

	int reflection;
	int i;
	unsigned short* x;
	unsigned short* z;
	EVENT* ev;
	MATRIX matrix;
	VECTOR pos;
	VECTOR shadow[4];
	XZPAIR offset;
	int thisCamera;
	int otherCamera;
	int temp;

#ifndef PSX
	if (gDemoLevel)
	{
		firstEvent = NULL;
		EventCop = NULL;
		return;
	}
#endif

	if (CurrentPlayerView == 0)
	{
		nearestTrain = NULL;

		thisCamera = 0x8000;
		otherCamera = 0x4000;
	}
	else
	{
		thisCamera = 0x4000;
		otherCamera = 0x8000;
	}

	if (camera == 0)
	{
		SetFrustrumMatrix();
	}
	else
	{
		if (cameraEventsActive == 0)
			return;

		InitEventCamera();
	}

	VisibilityLists(VIS_NEXT, CurrentPlayerView);

	x = xVis;
	while ((*x & thisCamera) == 0)
	{
		if ((*x & otherCamera) == 0)
		{
			if ((*x & 0x80) == 0)
				ev = &event[*x & 0x7f];
			else
				ev = (EVENT*)&fixedEvent[*x & 0x7f];

			z = zVis;

			while ((*z & thisCamera) == 0)
			{
				if ((*z & otherCamera) == 0 && (*x & 0xfff) == (*z & 0xfff))
				{
					if ((ev->flags & 4) == 0 && (ev->flags & 1) == camera)
					{
						ev->flags |= 4;

						if (camera == 0)
						{
							if (es_mobile[0] != -1 && (ev->flags & 2))
							{
								int dist;

								dist = ABS(ev->position.vx - camera_position.vx) + ABS(ev->position.vz - camera_position.vz);

								if (nearestTrain == NULL || dist < distanceFromNearestTrain)
								{
									nearestTrain = ev;
									distanceFromNearestTrain = dist;
								}
							}

							if (FrustrumCheck(&ev->position, modelpointers[ev->model]->bounding_sphere) != -1)
							{
								pos.vx = ev->position.vx - camera_position.vx;
								pos.vy = ev->position.vy - camera_position.vy;
								pos.vz = ev->position.vz - camera_position.vz;

								matrix.m[0][0] = ONE;
								matrix.m[1][0] = 0;
								matrix.m[2][0] = 0;
								matrix.m[0][1] = 0;
								matrix.m[1][1] = ONE;
								matrix.m[2][1] = 0;
								matrix.m[0][2] = 0;
								matrix.m[1][2] = 0;
								matrix.m[2][2] = ONE;

								reflection = 0;

								if ((ev->flags & 2U) == 0)
								{
									int type;

									type = ev->flags & 0xcc0;

									if (type == 0x80)
									{
										_RotMatrixY(&matrix, ev->rotation);

										pos.vx = pos.vx - boatOffset.vx;
										pos.vz = pos.vz - boatOffset.vz;
										pos.vy = (pos.vy - boatOffset.vy) + FIXEDH((int)ev->node * RSIN(*ev->data));
									}
									else if (type == 0x400)
									{
										_RotMatrixY(&matrix, ev->rotation);
									}
									else if (type == 0x440)
									{
										_RotMatrixX(&matrix, ev->rotation);
									}
									else if (type == 0x480)
									{
										int rot;

										for (i = 0; i < 4; i++)
										{
											if ((i & 1) == 0)
												offset.x = -201;
											else
												offset.x = 201;

											if ((i & 2) == 0)
												offset.z = -1261;
											else
												offset.z = 1261;

											rot = ev->rotation + 0x200U & 0x400;

											shadow[i].vx = ev->position.vx + (rot ? offset.z : offset.x);
											shadow[i].vy = 0;
											shadow[i].vz = ev->position.vz + (rot ? offset.x : offset.z);
										}

										PlaceShadowForCar(shadow, 0, 35, 4);
										_RotMatrixY(&matrix, ev->rotation);
									}
									else if (type != 0xc0)
									{
										type = ev->flags & 0x30;

										if (type == 0x20)
										{
											_RotMatrixZ(&matrix, ev->rotation);
										}
										else if (type == 0x10)
										{
											_RotMatrixY(&matrix, ev->rotation);
										}
										else if (type == 0)
										{
											_RotMatrixX(&matrix, ev->rotation);
										}
									}
								}
								else
								{
									_RotMatrixY(&matrix, ev->rotation);
								}

								gte_SetRotMatrix(&inv_camera_matrix);

								_MatrixRotate(&pos);

								if ((ev->flags & 0xcc0) == 0xc0)
								{
									if (ev->model == HelicopterData.cleanModel)
									{
										_RotMatrixZ(&matrix, HelicopterData.roll & 0xfff);
										_RotMatrixX(&matrix, HelicopterData.pitch & 0xfff);
										_RotMatrixY(&matrix, ev->rotation & 0xfff);

										DrawRotor(ev->position, &matrix);

										RenderModel(modelpointers[ev->model], &matrix, &pos, 0, 0, 1, 0);

										pos.vx = ev->position.vx - camera_position.vx;
										pos.vy = -camera_position.vy - ev->position.vy;
										pos.vz = ev->position.vz - camera_position.vz;

										gte_SetRotMatrix(&inv_camera_matrix);

										_MatrixRotate(&pos);
										reflection = 1;
									}
									else
									{
										// scale matrix lil bit
										matrix.m[0][0] = matrix.m[0][0] * 5 >> 2;
										matrix.m[1][1] = matrix.m[1][1] * 5 >> 2;
										matrix.m[2][2] = matrix.m[2][2] * 5 >> 2;

										_RotMatrixY(&matrix, 2813);
										RenderModel(modelpointers[ev->model], &matrix, &pos, 0, 0, 1, 0);
									}
								}

								if (ev->flags & 0x800)
								{
									_RotMatrixX(&matrix, ev->data[1]);

									pos.vy -= ev->data[2];
									temp = combointensity;

									if (ev->flags & 0x20)
									{
										SetupPlaneColours(0x80a0c);
									}
									else
									{
										ev->flags |= 1;
										reflection = 1;	// [A] fix bug with ferry reflection at night
									}
								}

								if (reflection)
								{
									matrix.m[1][0] = -matrix.m[1][0];
									matrix.m[1][1] = -matrix.m[1][1];
									matrix.m[1][2] = -matrix.m[1][2];

									SetupPlaneColours(0x00464a40);

									RenderModel(modelpointers[ev->model], &matrix, &pos, 400, PLOT_INV_CULL, 1, ev->rotation >> 6);
								}
								else
								{
									if ((ev->flags & 0xcc0U) == 0x440)
										DrawFerrisWheel(&matrix, &pos);
									else
										RenderModel(modelpointers[ev->model], &matrix, &pos, 0, 0, 1, ev->rotation >> 6);
								}

								if (ev->flags & 0x800)
								{
									combointensity = temp;

									matrix.m[0][0] = 0;
									matrix.m[0][1] = 0;
									matrix.m[0][2] = (foam.rotate & 0x8000) ? ONE : -ONE;
									matrix.m[1][0] = 0;
									matrix.m[1][1] = ONE;
									matrix.m[1][2] = 0;
									matrix.m[2][0] = ONE;
									matrix.m[2][1] = 0;
									matrix.m[2][2] = 0;

									pos.vy += ev->data[2];

									if (gTimeOfDay != 1)
										SetupPlaneColours(0x00282828);

									RenderModel(foam.model, &matrix, &pos, 200, (foam.rotate & 0x8000) ? 0x3 : 0x1, 1, 0);
									SetupPlaneColours(combointensity);
								}
							}
						}
						else
						{
							SetCamera(ev);
							DrawMapPSX(&ObjectDrawnValue);
						}
					}
				}
				z++;
			}
		}

		x++;
	}

	if (camera)
	{
		ResetEventCamera();
	}
	else if (eventHaze != 0)
	{
		add_haze(eventHaze, eventHaze, 7);
	}

	if (camera == 0 && (CurrentPlayerView == NumPlayers - 1) && es_mobile[0] != -1)
	{
		if (nearestTrain == NULL)
		{
			SetEnvSndVol(es_mobile[0], -10000);
		}
		else
		{
			SetEnvSndVol(es_mobile[0], 2000);
			SetEnvSndPos(es_mobile[0], nearestTrain->position.vx, nearestTrain->position.vz);
		}
	}
}

// [A] camera offset for events
void EventCameraOffset(SVECTOR* offset)
{
	offset->vx = 0;
	offset->vy = 0;
	offset->vz = 0;

	if (gCurrentMissionNumber == 22)
	{
		offset->vy = 650;
		offset->vz = -850;
	}
}

int debugOffset = 0;

// [D] [T]
sdPlane* EventSurface(VECTOR* pos, sdPlane* plane)
{
	int d;
	EVENT* ev;
	int d1, d2;
	int dist;
	int sin;
	int cos;

	int i;

	i = plane->surface & 0xffef;

	ev = &event[i];

	// chicago bridge plane
	if (GameLevel == 0)
	{
		int dist;
		int end;

		if ((ev->flags & 0x30U) == 0)
			dist = pos->vz;
		else
			dist = pos->vx;

		if ((i & 1) == 0)
			end = ev->data[1];
		else
			end = ev->data[-1];

		d = ev->rotation;
		sin = RSIN(d);
		cos = RCOS(d);

		d1 = (end - ev->data[0]) * cos;
		d2 = dist - ev->data[0];

		if (ABS(d1) < ABS(d2) * 4096)
			return GetSeaPlane();

		plane->d = d2 * sin / cos;

		if (d2 > 1024)
		{
			if (pos->vy + 200 < plane->d)
			{
				return GetSeaPlane();
			}

			plane->b = cos * 4;
		}
		else
		{
			plane->b = cos * 4;
		}

		if ((ev->flags & 0x30U) == 0)
		{
			plane->a = 0;
			plane->c = sin * -4;
			//plane->d = (sin * -0x40000) >> 0x10;	// [A] this is unused and incorrect!
		}
		else
		{
			plane->c = 0;
			plane->a = sin * -4;
		}

		plane->d ^= 0x40000000;
	}
	else if (GameLevel == 1 || GameLevel == 3)
	{
		// make secret base solid due to we use surface Ids
		// Havana 3D occlusion was made simpler in Rev 1.1
		// this change was very sneaky
		if (i > 8)
			return plane;
		
		// Havana and Rio boats floating
		if (ev->flags & 0x800)
		{
			int height;
			int offset;

			if (OnBoat(pos, ev, &dist) == 0)
				return GetSeaPlane();

			if (GameLevel == 3)
				height = 256;
			else
				height = 200;


			d = ev->data[1];

			sin = RSIN(d);
			cos = RCOS(d);

			offset = dist * -4096 + cos * -3328;

			if (GameLevel == 3 && offset > 0)
			{
				int sin2;
				d = 160 - ev->data[1];

				cos = RCOS(d);
				sin2 = RSIN(d);

				if (offset > cos * 2048)
				{
					debugOffset = offset;
					return GetSeaPlane();
				}

				plane->d = height - (FIXEDH(sin * 3328) + ev->data[2] + ev->position.vy) + (FIXEDH(offset) * sin2 / cos) ^ 0x40000000;

				plane->b = cos * 4;
				plane->a = 0;
				plane->c = sin2 * 4;
			}
			else
			{
				plane->b = cos * 4;
				plane->a = 0;
				plane->c = sin * 4;
				plane->d = (height + (sin * dist / cos - ev->data[2] - ev->position.vy)) ^ 0x40000000;
			}
		}
		else
		{
			if (pos->vy < -ev->position.vy - 100)
			{
				plane->d = 0xbfffee02;
			}
			else
			{
				plane->d = ev->position.vy;
			}
		}
	}
	else
	{
		return GetSeaPlane();
	}

	return plane;
}

// [D] [T]
void MakeEventTrackable(EVENT* ev)
{
	EVENT** p;

	p = trackingEvent;
	while (*p)
		p++;

	*p = ev;
	p[1] = NULL;	// WTF?
}

// [D] [T]
void TriggerDoor(FixedEvent* door, int* stage, int sound)
{
	if (*stage == 0)
	{
		if ((door->flags & 0x30) != 0x10)
			door->flags &= ~0x200;

		door->active = 2;
	}
	else
	{
		door->active = 1;
		door->flags |= 0x200;

		*stage = -1;
	}

	if (sound != 0)
		SetMSoundVar(1, &door->position);
}

// [D] [T]
VECTOR* TriggerEvent(int i)
{
	static int stage[10];

	EVENT* ev;
	int loop;
	VECTOR* pos;

	pos = NULL;

	if (i == -1) // initialize
	{
		for (loop = 0; loop < 10; loop++)
			stage[loop] = 0;

		return NULL;
	}

	if (GameLevel >= 2 && i > 0 && i < 4) // Vegas and Rio detonators
	{
		if (stage[i] == 0)
		{
			ev = &firstMissionEvent[i - 1];

			ev->next = firstEvent->next;
			firstEvent->next = ev;

			VisibilityLists(VIS_ADD, ev - event);
			SetMSoundVar(i, NULL);

			detonator.count++;
		}
	}
	else
	{
		if (GameLevel == 0)
		{
			switch (i)
			{
				case 0:
				case 1:
					if (stage[i] == 0)
					{
						int offset, nodePos;
						
						ev = missionTrain[i].engine;
						
						MakeEventTrackable(ev);
						ev->flags |= 0x180;

						if (*missionTrain[i].node - missionTrain[i].start > -1)
							offset = 1600;
						else
							offset = -1600;

						loop = 0;

						do {
							if (missionTrain[i].node[0] == PATH_NODE_STATION)
								nodePos = missionTrain[i].node[-1];
							else
								nodePos = missionTrain[i].node[1];

							if (missionTrain[i].startDir == 0x8000)
							{
								ev->flags |= 0x8000;
								ev->position.vx = nodePos;
								ev->position.vz = missionTrain[i].start + loop * offset;
							}
							else
							{
								ev->flags &= ~0x8000;
								ev->position.vz = nodePos;
								ev->position.vx = missionTrain[i].start + loop * offset;
							}

							ev->node = missionTrain[i].node;
							ev->data = &missionTrain[i].cornerSpeed;
							ev->timer = 0;
							
							ev->flags &= ~0x7000;
							ev->flags |= 0x3000;

							SetElTrainRotation(ev);

							loop++;
							ev = ev->next;
						} while (ev && (ev->flags & 0x400U) == 0);
					}
					else
					{
						ev = missionTrain[i].engine;
						
						pos = &ev->position;
						
						if (ev->timer != 0)
							ev->timer = 1;

					}
					break;
				case 2:
				case 3:
				case 4:
					if (stage[i] == 0)
					{
						// start bridges raised
						ev = firstMissionEvent;
						for (loop = 0; loop < 10; loop++)
						{
							ev->flags |= 0x100;

							if (i == 4)
								ev->timer = 2600;
							else
								ev->timer = 1000;
							
							ev++;
						}

						if (i == 2)
						{
							firstMissionEvent[9].timer = 2600;
							firstMissionEvent[8].timer = 2600;
						}
					}
					else
					{
						// raise bridges
						firstMissionEvent[8].timer = 0;
						firstMissionEvent[9].timer = 0;
					}
					break;
				case 5:
					PrepareSecretCar();
					events.cameraEvent = (EVENT*)&chicagoDoor[0];
				case 6:
					TriggerDoor(&chicagoDoor[i - 5], &stage[i], 1);
			}
		}
		else if (GameLevel == 1)
		{
			switch (i)
			{
				case 0:
					event->timer = 1;
					break;
				case 1:
					event->position.vx = HavanaFerryData[9];
					event->position.vz = HavanaFerryData[10];
				
					event->timer = 1;
				
					event->node = &HavanaFerryData[10];
					event->data = &HavanaFerryData[6];

					// [A] reset Ferry angles
					event->data[1] = RSIN(CameraCnt * 32) >> 9;
					event->data[2] = RCOS(CameraCnt * 16) + 4096 >> 7;
				
					break;
				case 2:
					TriggerDoor(&havanaFixed[0], &stage[i], 1);
					break;
				case 3:
					PrepareSecretCar();
					events.cameraEvent = (EVENT*)&havanaFixed[2];
					TriggerDoor(&havanaFixed[2], &stage[i], 0);
					break;
				case 4:
					if (stage[i] != 0)
					{
						SetSpecialCamera(SPECIAL_CAMERA_WAIT, 0);
						event[1].node++;
					}

					SetMSoundVar(1, &event[1].position);

					event[1].timer = 0;
					events.cameraEvent = &event[1];
			}
		}
		else if (GameLevel == 2)
		{
			switch (i)
			{
				case 0:
					loop = 0;

					// start train
					do {
						ev = &event[loop];

						ev->data = VegasTrainData;
						
						InitTrain(ev, loop, 1);
						
						ev->flags |= 0x200;

						if (loop > 1)
							VisibilityLists(VIS_ADD, loop);

						loop++;
					} while (loop < 9);

					event->flags |= 0x500;

					MakeEventTrackable(ev);

					event[1].next = &event[2];
					break;
				case 4:
					TriggerDoor(&vegasDoor[i - 4], &stage[i], 0);
					break;
				case 8:
					events.cameraEvent = (EVENT*)&vegasDoor[4];
					PrepareSecretCar();
				case 5:
				case 6:
				case 7:
					TriggerDoor(&vegasDoor[i - 4], &stage[i], 1);
					break;
				case 9:
					SetMSoundVar(5, NULL);
			}
		}
		else if (GameLevel == 3)
		{
			switch (i)
			{
				case 0:
					event->timer = 1;
					break;
				case 4:
					// open race track gates
					TriggerDoor(&rioDoor[2], &stage[i], 0);
					TriggerDoor(&rioDoor[3], &stage[i], 0);

					events.cameraEvent = (EVENT*)&rioDoor[2];
					break;
				case 5:
				case 6:
					TriggerDoor(&rioDoor[i - 5], &stage[i], (i == 5));
					break;
				case 7:
					if (stage[i] == 0)
					{
						pos = &event[1].position;
					}
					else
					{
						event[1].position.vy = -17;
						event[1].position.vx = -241619;
						event[1].position.vz = -212638;
						event[1].timer = -2;
						event[1].model = HelicopterData.deadModel;
					}

					break;
				case 8:
					// open gate to secret car
					PingOutAllSpecialCivCars();
				
					TriggerDoor(&rioDoor[4], &stage[i], 0);
					TriggerDoor(&rioDoor[5], &stage[i], 0);
				
					events.cameraEvent = (EVENT*)&rioDoor[4];
			}
		}
	}

	if (i < 10)
		stage[i]++;

	return pos;
}

// [D] [T]
void OffsetTarget(VECTOR* target)
{
	target->vx -= boatOffset.vx;
	target->vy -= boatOffset.vy;
	target->vz -= boatOffset.vz;
}

//[D] [T]
void SetSpecialCamera(SpecialCamera type, int change)
{
	static int rememberCamera[3]; // offset 0x230
	static short boatCamera[6] = {
		-1000, 2100, 6000,
		-5000, 3584, -5000
	};

	int* hackCamera;
	short* boat;

	if (change == 0 && type != SPECIAL_CAMERA_WAIT)
	{
		cameraDelay.delay = 1;
		cameraDelay.type = type;
		camera_change = 1;
		return;
	}

	if (type == SPECIAL_CAMERA_RESET)
	{
		events.cameraEvent = NULL;
		gStopPadReads = 0;

		camera_position.vx = rememberCamera[0];
		camera_angle.vy = (short)rememberCamera[1];
		camera_position.vz = rememberCamera[2];

		player[0].cameraPos.vx = rememberCamera[0];
		player[0].cameraPos.vy = camera_position.vy;
		player[0].cameraPos.vz = rememberCamera[2];
		player[0].cameraPos.pad = camera_position.pad;
	}
	else if (type == SPECIAL_CAMERA_WAIT)
	{
		cameraDelay.delay = 100;
		cameraDelay.type = 1;
		gStopPadReads = 1;
	}
	else
	{
		boat = NULL;

		rememberCamera[0] = camera_position.vx;
		rememberCamera[1] = camera_angle.vy;
		rememberCamera[2] = camera_position.vz;

		if (GameLevel == 0)
		{
			hackCamera = &ChicagoCameraHack[0];
		}
		else if (GameLevel == 1)
		{
			if (gCurrentMissionNumber == 15)
			{
				boat = boatCamera;
				events.cameraEvent = (EVENT*)0x1;
			}
			else
			{
				if (type == SPECIAL_CAMERA_SET2)
				{
					hackCamera = &HavanaCameraHack[6];
					camera_position.vy = -500;
				}
				else if (doneFirstHavanaCameraHack)
				{
					hackCamera = &HavanaCameraHack[3];
				}
				else
				{
					hackCamera = &HavanaCameraHack[0];
					doneFirstHavanaCameraHack = 1;
				}
			}
		}
		else if (GameLevel == 2)
		{
			hackCamera = &VegasCameraHack[0];

			if (gCurrentMissionNumber == 23)
			{
				if (type != SPECIAL_CAMERA_SET2)
				{
					hackCamera = &VegasCameraHack[6];
					events.cameraEvent = (EVENT*)0x1;
				}
				else
				{
					hackCamera = &VegasCameraHack[9];
				}
			}
			else if (gCurrentMissionNumber == 22)
			{
				hackCamera = &VegasCameraHack[13];

				events.cameraEvent = (EVENT*)0x1;
				camera_position.vy = -1800;
			}
			else if (gCurrentMissionNumber == 30)
			{
				hackCamera += 3;
			}
		}
		else if (GameLevel == 3)
		{
			if (gCurrentMissionNumber == 35)
			{
				boat = &boatCamera[3];
			}
			else if (camera_position.vz > 0)
			{
				hackCamera = &RioCameraHack[3];
			}
			else
			{
				hackCamera = &RioCameraHack[0];
			}
		}

		if (boat == NULL)
		{
			camera_position.vx = hackCamera[0];
			camera_angle.vy = hackCamera[1];
			camera_position.vz = hackCamera[2];
		}
		else
		{
			camera_position.vx = event->position.vx + boat[0];
			camera_angle.vy = boat[1];
			camera_position.vz = event->position.vz + boat[2];
		}

		player[0].cameraPos = camera_position;

		if (events.cameraEvent != NULL)
		{
			gStopPadReads = 1;
		}
	}

	if (type != SPECIAL_CAMERA_WAIT)
	{
		Havana3DLevelDraw = -1;
		camera_change = 1;
		
		VisibilityLists(VIS_SORT, 0);
	}
}

// [D] [T]
void ScreenShake(int count, SVECTOR* ang)
{
	eventHaze = count * 4;

	camera_angle.vx = (ang->vx + (Random2(0) % (eventHaze / 2))) - eventHaze;
	camera_angle.vy = (ang->vy + (Random2(0) % (eventHaze * 2))) - eventHaze;

	eventHaze *= 3;
}

// [D] [T]
int DetonatorTimer(void)
{
	static SVECTOR rememberCameraAngle; // offset 0x30
	static int count = 0; // offset 0x38

	int cnt;
	EVENT* ev;
	VECTOR pos;

	if (gCurrentMissionNumber == 23)
	{
		if (detonator.timer - 3U < 17)
		{
			ScreenShake(detonator.timer - 2, &rememberCameraAngle);
		}
		else if (detonator.timer - 31U > 8)
		{
			if (detonator.timer == 21)
			{
				count++;

				if ((count & 7) == 0)
				{
					AddExplosion(camera_position, BIG_BANG);
				}

				camera_position.vz += 150;

				if (VegasCameraHack[12] < camera_position.vz)
				{
					pos.vx = camera_position.vx;
					pos.vy = camera_position.vy;
					pos.vz = 271695;

					AddExplosion(pos, HEY_MOMMA);

					rememberCameraAngle = camera_angle;
					
					Mission.timer[0].flags &= ~TIMER_FLAG_BOMB_COUNTDOWN;
					
					SetMissionComplete();
				}
				else
				{
					detonator.timer = detonator.timer + 1;
				}

				player[0].cameraPos.vz = camera_position.vz;
			}
			else if (detonator.timer == 0)
			{
				SetSpecialCamera(SPECIAL_CAMERA_SET, 0);
				detonator.timer = 70;
			}
			else if (detonator.timer == 22)
			{
				SetSpecialCamera(SPECIAL_CAMERA_SET2, 0);
			}
			else if (detonator.timer == 40)
			{
				BombThePlayerToHellAndBack(gCarWithABerm);
				car_data[gCarWithABerm].st.n.linearVelocity[1] += 200000;
				rememberCameraAngle = camera_angle;
			}
		}
		else
		{
			ScreenShake(detonator.timer - 30, &rememberCameraAngle);
		}
	}
	else
	{
		if (detonator.timer - 141U < 19)
		{
			ScreenShake(detonator.timer - 140, &rememberCameraAngle);
			Setup_Smoke(&firstMissionEvent[0].position, 100, 500, SMOKE_BLACK, 0, &dummy, 0);
		}
		else
		{
			if (detonator.timer == 167)
			{
				ev = &firstMissionEvent[1];
				ev++;
			}
			else
			{
				if (detonator.timer < 168)
				{
					if (detonator.timer == 0)
					{
						cnt = detonator.count - 1;
						ev = &firstMissionEvent[0];

						if (detonator.count < 3)
						{
							while (detonator.count = cnt, detonator.count != -1)
							{
								AddExplosion(ev->position, BIG_BANG);
								cnt = detonator.count - 1;
								ev++;
							}

							return 0;
						}

						detonator.timer = 200;
						SetSpecialCamera(SPECIAL_CAMERA_SET, 0);
						events.cameraEvent = (EVENT*)0x1;

						rememberCameraAngle = camera_angle;
					}
					else if (detonator.timer == 160)
					{

						if (GameLevel == 3)
						{
							event->flags &= ~0x1;
							event->flags |= 0x20;

							AddExplosion(event[0].position, HEY_MOMMA);
						}
						else
						{
							AddExplosion(firstMissionEvent[1].position, HEY_MOMMA);
						}


					}

					detonator.timer--;
					return 1;
				}

				ev = &firstMissionEvent[0];

				if (detonator.timer == 180)
				{
					ev++;
				}
				else if (detonator.timer != 190)
				{
					detonator.timer--;
					return 1;
				}
			}

			pos.vx = ev->position.vx;
			pos.vy = ev->position.vy;
			pos.vz = ev->position.vz;

			if (GameLevel == 3)
			{
				pos.vx -= boatOffset.vx;
				pos.vy -= boatOffset.vy;
				pos.vz -= boatOffset.vz;
			}

			AddExplosion(pos, BIG_BANG);
		}
	}

	detonator.timer--;
	return 1;
}

// [D] [T]
void MultiCarEvent(MS_TARGET* target)
{
	EVENT* first;
	int n;
	EVENT* ev;
	MULTICAR_DATA* mcd;
	int i;

	first = firstEvent;

	// [A] validate
	if (target->type != Target_MultiCar)
		return;
	
	firstEvent = multiCar.event + multiCar.count;

	for (i = 0; i < 5; i++)
	{
		mcd = &target->multiCar[i];

		if (mcd->x == 0x80000000)
			break;
		
		n = (multiCar.event - event) + multiCar.count;

		ev = event + n;

		ev->position.vx = mcd->x;
		ev->position.vz = mcd->z;
		
		ev->position.vy = -312;
	
		ev->rotation = mcd->rot;

		VisibilityLists(VIS_ADD, n);

		multiCar.count++;
	}

	//firstEvent->next = first; // [A] bug fix
	multiCar.event[multiCar.count - 1].next = first;
}