diff --git a/src/control/Script.cpp b/src/control/Script.cpp
index 224f9089..fbb7024c 100644
--- a/src/control/Script.cpp
+++ b/src/control/Script.cpp
@@ -1662,7 +1662,7 @@ const tScriptCommandData commands[] = {
 	REGISTER_COMMAND(COMMAND_IS_CHAR_CROUCHING, INPUT_ARGUMENTS(ARGTYPE_INT, ), OUTPUT_ARGUMENTS(), true, -1, ""),
 	REGISTER_COMMAND(COMMAND_GET_FERRY_BOARDING_SPACE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ), false, -1, ""),
 	REGISTER_COMMAND(COMMAND_GET_FERRY_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ), false, -1, ""),
-	REGISTER_COMMAND(COMMAND_SET_FERRIES_ENABLED, INPUT_ARGUMENTS(ARGTYPE_INT, ), OUTPUT_ARGUMENTS(), false, -1, ""),
+	REGISTER_COMMAND(COMMAND_SET_FERRIES_DISABLED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ), OUTPUT_ARGUMENTS(), false, -1, ""),
 	REGISTER_COMMAND(COMMAND_COMPLETE_FERRY_DOOR_MOVEMENT, INPUT_ARGUMENTS(ARGTYPE_INT, ), OUTPUT_ARGUMENTS(), false, -1, ""),
 	REGISTER_COMMAND(COMMAND_OVERRIDE_CAR_REMOTE_CONTROL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ), OUTPUT_ARGUMENTS(), false, -1, ""),
 	REGISTER_COMMAND(COMMAND_CANCEL_REMOTE_MODE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""),
diff --git a/src/control/Script9.cpp b/src/control/Script9.cpp
index f6ebc299..579626f3 100644
--- a/src/control/Script9.cpp
+++ b/src/control/Script9.cpp
@@ -7,6 +7,7 @@
 #include "CarCtrl.h"
 #include "Camera.h"
 #include "CutsceneMgr.h"
+#include "Ferry.h"
 #include "Garages.h"
 #include "GameLogic.h"
 #include "Hud.h"
@@ -29,22 +30,22 @@ int8 CRunningScript::ProcessCommands1500To1599(int32 command)
 	case COMMAND_DISABLE_FERRY_PATH:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry:DissableFerryPath(GET_INTEGER_PARAM(0)); TODO
+		CFerry::DissableFerryPath(GET_INTEGER_PARAM(0));
 		return 0;
 	}
 	case COMMAND_ENABLE_FERRY_PATH:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry::EnableFerryPath(GET_INTEGER_PARAM(0));
+		CFerry::EnableFerryPath(GET_INTEGER_PARAM(0));
 		return 0;
 	}
 	case COMMAND_GET_CLOSEST_DOCKED_FERRY:
 	{
 		CollectParameters(&m_nIp, 2);
-		// CFerry* pFerry = CFerry::GetClosestFerry(GET_FLOAT_PARAM(0), GET_FLOAT_PARAM(1));
+		CFerry* pFerry = CFerry::GetClosestFerry(GET_FLOAT_PARAM(0), GET_FLOAT_PARAM(1));
 		int id = -1;
-		// if (pFerry && pFerry->IsDocked()
-		//	id = pFerry->GetId();
+		if (pFerry && pFerry->IsDocked())
+			id = pFerry->m_nFerryId;
 		SET_INTEGER_PARAM(0, id);
 		StoreParameters(&m_nIp, 1);
 		return 0;
@@ -52,43 +53,41 @@ int8 CRunningScript::ProcessCommands1500To1599(int32 command)
 	case COMMAND_OPEN_FERRY_DOOR:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// pFerry->OpenDoor();
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		pFerry->OpenDoor();
 		return 0;
 	}
 	case COMMAND_CLOSE_FERRY_DOOR:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// pFerry->CloseDoor();
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		pFerry->CloseDoor();
 		return 0;
 	}
 	case COMMAND_IS_FERRY_DOOR_OPEN:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// UpdateCompareFlag(pFerry->IsDoorOpen());
-		UpdateCompareFlag(false);
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		UpdateCompareFlag(pFerry->IsDoorOpen());
 		return 0;
 	}
 	case COMMAND_IS_FERRY_DOOR_CLOSED:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// UpdateCompareFlag(pFerry->IsDoorClosed());
-		UpdateCompareFlag(true);
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		UpdateCompareFlag(pFerry->IsDoorClosed());
 		return 0;
 	}
 	case COMMAND_SKIP_FERRY_TO_NEXT_DOCK:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// pFerry->SkipFerryToNextDock();
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		pFerry->SkipFerryToNextDock();
 		return 0;
 	}
 	case COMMAND_SET_CHAR_DROPS_WEAPONS_ON_DEATH:
@@ -110,35 +109,36 @@ int8 CRunningScript::ProcessCommands1500To1599(int32 command)
 	case COMMAND_GET_FERRY_BOARDING_SPACE:
 	{
 		CollectParameters(&m_nIp, 4);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// ? = pFerry->GetBoardingSpace((CFerry::eSpaceUse)GET_INTEGER_PARAMS(1), (CFerry::eSpaceStyle)GET_INTEGER_PARAMS(2), GET_INTEGER_PARAMS(3));
-		SET_FLOAT_PARAM(0, 0.0f);
-		SET_FLOAT_PARAM(1, 0.0f); // TODO
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		CVector space = pFerry->GetBoardingSpace((CFerry::eSpaceUse)GET_INTEGER_PARAM(1), (CFerry::eSpaceStyle)GET_INTEGER_PARAM(2), GET_INTEGER_PARAM(3));
+		SET_FLOAT_PARAM(0, space.x);
+		SET_FLOAT_PARAM(1, space.y);
 		StoreParameters(&m_nIp, 2);
 		return 0;
 	}
 	case COMMAND_GET_FERRY_HEADING:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
-		// script_assert(pFerry);
-		// float fHeading = CGeneral::GetATanOfXY(pFerry->GetForward().x, pFerry->GetForward().y);
-		// SET_FLOAT_PARAM(0, fHeading);
-		SET_FLOAT_PARAM(0, 0.0f);
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		float fHeading = Atan2(-pFerry->GetForward().x, pFerry->GetForward().y);
+		SET_FLOAT_PARAM(0, fHeading);
 		StoreParameters(&m_nIp, 1);
 		return 0;
 	}
-	case COMMAND_SET_FERRIES_ENABLED:
+	case COMMAND_SET_FERRIES_DISABLED:
 	{
-		CollectParameters(&m_nIp, 1);
-		// CFerry::SetFerriesEnabled(GET_INTEGER_PARAM(0));
+		CollectParameters(&m_nIp, 2);
+		CFerry::SetFerriesDisabled(GET_INTEGER_PARAM(1));
 		return 0;
 	}
 	case COMMAND_COMPLETE_FERRY_DOOR_MOVEMENT:
 	{
 		CollectParameters(&m_nIp, 1);
-		// CFerry::CompleteDorrMovement(GET_INTEGER_PARAM(0));
+		CFerry* pFerry = CFerry::GetFerry(GET_INTEGER_PARAM(0));
+		script_assert(pFerry);
+		pFerry->CompleteDorrMovement();
 		return 0;
 	}
 	case COMMAND_OVERRIDE_CAR_REMOTE_CONTROL:
@@ -157,7 +157,7 @@ int8 CRunningScript::ProcessCommands1500To1599(int32 command)
 		else {
 			TheCamera.TakeControl(pVehicle, CCam::MODE_1STPERSON, GET_INTEGER_PARAM(1) ? INTERPOLATION : JUMP_CUT, CAMCONTROL_SCRIPT);
 			script_assert(pVehicle->IsCar());
-			//((CAutomobile*)pVehicle)->Damage.m_bSmashedDoorDoesntClose = true;
+			((CAutomobile*)pVehicle)->Damage.m_bSmashedDoorDoesntClose = true;
 		}
 		if (m_bIsMissionScript)
 			CTheScripts::MissionCleanUp.RemoveEntityFromList(GET_INTEGER_PARAM(0), CLEANUP_CAR);
@@ -670,7 +670,7 @@ int8 CRunningScript::ProcessCommands1500To1599(int32 command)
 	}
 	case COMMAND_SWITCH_FERRY_COLLISION:
 		CollectParameters(&m_nIp, 1);
-		// CFerry::SwitchFerryCollision(GET_INTEGER_PARAM(0));
+		CFerry::SwitchFerryCollision(GET_INTEGER_PARAM(0));
 		return 0;
 	case COMMAND_SET_CHAR_MAX_HEALTH:
 	{
diff --git a/src/control/ScriptCommands.h b/src/control/ScriptCommands.h
index 000f561c..93f670a9 100644
--- a/src/control/ScriptCommands.h
+++ b/src/control/ScriptCommands.h
@@ -1510,7 +1510,7 @@ enum {
 	COMMAND_IS_CHAR_CROUCHING,
 	COMMAND_GET_FERRY_BOARDING_SPACE,
 	COMMAND_GET_FERRY_HEADING,
-	COMMAND_SET_FERRIES_ENABLED,
+	COMMAND_SET_FERRIES_DISABLED,
 	COMMAND_COMPLETE_FERRY_DOOR_MOVEMENT,
 	COMMAND_OVERRIDE_CAR_REMOTE_CONTROL,
 	COMMAND_CANCEL_REMOTE_MODE,
diff --git a/src/core/Game.cpp b/src/core/Game.cpp
index 49e9c0da..a370c9d4 100644
--- a/src/core/Game.cpp
+++ b/src/core/Game.cpp
@@ -22,6 +22,7 @@
 #include "Darkel.h"
 #include "Debug.h"
 #include "EventList.h"
+#include "Ferry.h"
 #include "FileLoader.h"
 #include "FileMgr.h"
 #include "Fire.h"
@@ -547,6 +548,7 @@ bool CGame::Initialise(const char* datFile)
 	LoadingScreen("Loading the Game", "Position dynamic objects", nil);
 	LoadingScreen("Loading the Game", "Initialise vehicle paths", nil);
 
+	CFerry::InitFerrys();
 	CTrain::InitTrains();
 	CPlane::InitPlanes();
 	CCredits::Init();
@@ -721,6 +723,7 @@ void CGame::ReInitGameObjectVariables(void)
 		CTheScripts::StartTestScript();
 		CTheScripts::Process();
 		TheCamera.Process();
+		CFerry::InitFerrys();
 		CTrain::InitTrains();
 		CPlane::InitPlanes();
 	}
@@ -806,6 +809,7 @@ void CGame::InitialiseWhenRestarting(void)
 		if ( GenericLoad() == true )
 		{
 			DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds());
+			CFerry::InitFerrys();
 			CTrain::InitTrains();
 			CPlane::InitPlanes();
 		}
@@ -889,6 +893,7 @@ void CGame::Process(void)
 
 		CCollision::Update();
 		CScriptPaths::Update();
+		CFerry::UpdateFerrys();
 		CTrain::UpdateTrains();
 		CPlane::UpdatePlanes();
 		CHeli::UpdateHelis();
diff --git a/src/core/config.h b/src/core/config.h
index e491c317..d8d62782 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -147,7 +147,9 @@ enum Config {
 	NUM_EXPLOSIONS = 48,
 
 	NUM_SETPIECES = 96,
-	NUM_SHORTCUT_START_POINTS = 16
+	NUM_SHORTCUT_START_POINTS = 16,
+
+	NUM_FERRY_PATHS = 1
 };
 
 // We don't expect to compile for PS2 or Xbox
diff --git a/src/entities/Entity.h b/src/entities/Entity.h
index 4db58eef..239292c5 100644
--- a/src/entities/Entity.h
+++ b/src/entities/Entity.h
@@ -24,11 +24,13 @@ enum eEntityStatus
 	STATUS_PHYSICS,
 	STATUS_ABANDONED,
 	STATUS_WRECKED,
-	STATUS_TRAIN_MOVING,	// these probably copied for FERRY
+	STATUS_TRAIN_MOVING,
 	STATUS_TRAIN_NOT_MOVING,
+	STATUS_FERRY_MOVING,
+	STATUS_FERRY_NOT_MOVING,
 	STATUS_HELI,
 	STATUS_PLANE,
-	STATUS_PLAYER_REMOTE,	// 12 in LCS
+	STATUS_PLAYER_REMOTE,
 	STATUS_PLAYER_DISABLED,
 	STATUS_GHOST
 };
diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp
index 6e4b3a3e..85c52032 100644
--- a/src/renderer/Renderer.cpp
+++ b/src/renderer/Renderer.cpp
@@ -27,6 +27,7 @@
 #include "Renderer.h"
 #include "custompipes.h"
 #include "Frontend.h"
+#include "Ferry.h"
 
 bool gbShowPedRoadGroups;
 bool gbShowCarRoadGroups;
@@ -546,6 +547,8 @@ CRenderer::RenderVehicles(void)
 	    node != &gSortedVehiclesAndPeds.head;
 	    node = node->prev)
 		RenderOneNonRoad(node->item.ent);
+
+	CFerry::RenderAllRemaning();
 	POP_RENDERGROUP();
 }
 
diff --git a/src/vehicles/Ferry.cpp b/src/vehicles/Ferry.cpp
new file mode 100644
index 00000000..89a0de9f
--- /dev/null
+++ b/src/vehicles/Ferry.cpp
@@ -0,0 +1,832 @@
+#include "common.h"
+#include "main.h"
+#include "Ferry.h"
+
+#include "AudioManager.h"
+#include "Camera.h"
+#include "Coronas.h"
+#include "FileMgr.h"
+#include "General.h"
+#include "Leeds.h"
+#include "Particle.h"
+#include "PlayerPed.h"
+#include "Streaming.h"
+#include "TempColModels.h"
+#include "WaterLevel.h"
+#include "World.h"
+
+CFerryInst* CFerry::mspInst;
+
+#define FERRY_SPEED (0.1f)
+#define FERRY_SLOWDOWN_DISTANCE (50.0f)
+#define FERRY_TIME_STOPPED_AT_STATION (10.0f)
+
+CFerry::CFerry(int32 id, uint8 owner) : CVehicle(owner)
+{
+	m_bPlayerArrivedHorn = false;
+	m_nTimeAlongPath = 0;
+	m_vehType = VEHICLE_TYPE_FERRY;
+	CVehicleModelInfo* mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
+	pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
+	SetModelIndex(id);
+	m_doors[0].Init(DEGTORAD(90.0f), 0.0f, 1, 0);
+	m_doors[1].Init(DEGTORAD(-95.0f), 0.0f, 1, 0);
+	m_doors[2].Init(DEGTORAD(-90.0f), 0.0f, 1, 0);
+	m_doors[3].Init(DEGTORAD(95.0f), 0.0f, 1, 0);
+	m_fTurnMass = 100000000.0f;
+	m_fAirResistance = 0.9994f;
+	m_fElasticity = 0.05f;
+	m_nNumMaxPassengers = 1;
+	m_fMass = 100000000.0f;
+	bInfiniteMass = true;
+	m_phy_flagA08 = true;
+	m_bFerryDocked = false;
+	SetStatus(STATUS_FERRY_MOVING);
+	bUsesCollision = true;
+	m_nDoorTimer = CTimer::GetTimeInMilliseconds();
+	m_nDoorState = FERRY_DOOR_CLOSED;
+	m_bApproachingDock = false;
+	m_nSkipFerryStatus = 0;
+	m_nCollision = 0;
+	m_pDefaultColModel = mi->GetColModel();
+	m_level = LEVEL_GENERIC;
+}
+
+void CFerry::Init(void* pInstancePtr)
+{
+	mspInst = (CFerryInst*)pInstancePtr;
+	if (mspInst)
+		return;
+	// the following code should be wrapped in a define
+	mspInst = new CFerryInst();
+	memset(mspInst, 0, sizeof(CFerryInst));
+	for (int k = 0; k < NUM_FERRY_PATHS; k++) {
+		mspInst->pPathData[k] = new CFerryPath();
+		mspInst->pPathData[k]->aLineBits = new CFerryInterpolationLine[NUM_FERRY_STATIONS * 4 + 2];
+
+		const char* filename = "Data\\PATHS\\FERRY1.DAT"; // actually base::cStringT<char> filename; filename += k+1; filename += ".DAT"
+
+		bool readingFile = false;
+		int bp, lp;
+		int i, tmp;
+		CFerryPath* pPath = mspInst->pPathData[k];
+
+		if (pPath->aTrackNodes == nil) {
+			readingFile = true;
+
+			CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
+			*gString = '\0';
+			for (bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++)
+				gString[lp] = work_buff[bp];
+			bp++;
+#ifdef FIX_BUGS
+			gString[lp] = '\0';
+#endif
+			sscanf(gString, "%d", &tmp);
+			pPath->NumTrackNodes = tmp;
+			pPath->aTrackNodes = new CFerryNode[tmp];
+
+			for (i = 0; i < pPath->NumTrackNodes; i++) {
+				*gString = '\0';
+				for (lp = 0; work_buff[bp] != '\n'; bp++, lp++)
+					gString[lp] = work_buff[bp];
+				bp++;
+#ifdef FIX_BUGS
+				gString[lp] = '\0';
+#endif
+				sscanf(gString, "%f %f %f", &pPath->aTrackNodes[i].x, &pPath->aTrackNodes[i].y, &pPath->aTrackNodes[i].z);
+				pPath->aTrackNodes[i].z = 0.0f;
+			}
+		}
+
+		// Calculate length of segments and track
+		float t = 0.0f;
+		for (i = 0; i < pPath->NumTrackNodes; i++) {
+			pPath->aTrackNodes[i].t = t;
+			t += Sqrt(SQR(pPath->aTrackNodes[(i + 1) % pPath->NumTrackNodes].x - pPath->aTrackNodes[i].x) +
+				(SQR(pPath->aTrackNodes[(i + 1) % pPath->NumTrackNodes].y - pPath->aTrackNodes[i].y)));
+		}
+		pPath->TotalLengthOfTrack = t;
+
+		// Find correct z values
+		if (readingFile) {
+			CColPoint colpoint;
+			CEntity* entity;
+			for (i = 0; i < pPath->NumTrackNodes; i++) {
+				CVector p(pPath->aTrackNodes[i].x, pPath->aTrackNodes[i].y, pPath->aTrackNodes[i].z + 1.0f);
+				if (CWorld::ProcessVerticalLine(p, p.z - 0.5f, colpoint, entity, true, false, false, false, true, false, nil))
+					pPath->aTrackNodes[i].z = colpoint.point.z;
+				pPath->aTrackNodes[i].z += 0.2f;
+			}
+		}
+
+		int nStationIndices[NUM_FERRY_STATIONS];
+		for (int i = 0; i < NUM_FERRY_STATIONS; i++)
+			nStationIndices[i] = 0;
+		int nCurrentStation = 0;
+
+		for (i = 0; i < pPath->NumTrackNodes; i++) {
+			CFerryNode* pCurNode = &pPath->aTrackNodes[i];
+			CFerryNode* pNextNode = (i + 1 < pPath->NumTrackNodes) ? &pPath->aTrackNodes[i + 1] : &pPath->aTrackNodes[0];
+			CFerryNode* pPrevNode = (i - 1 >= 0) ? &pPath->aTrackNodes[i - 1] : &pPath->aTrackNodes[pPath->NumTrackNodes - 1];
+			if (pCurNode->x - pNextNode->x > 0.0f && pPrevNode->x - pCurNode->x < 0.0f)
+				nStationIndices[nCurrentStation++] = i;
+			if (pCurNode->x - pNextNode->x < 0.0f && pPrevNode->x - pCurNode->x > 0.0f)
+				nStationIndices[nCurrentStation++] = i;
+		}
+
+		float stationDists[NUM_FERRY_STATIONS];
+		for (i = 0; i < NUM_FERRY_STATIONS; i++)
+			stationDists[i] = pPath->aTrackNodes[nStationIndices[i]].t;
+
+		// Create animation for stopping at stations
+		float position = 0.0f;
+		float time = 0.0f;
+		int j = 0;
+		for (i = 0; i < NUM_FERRY_STATIONS; i++) {
+			// Start at full speed
+			pPath->aLineBits[j].type = FERRY_CRUISING;
+			pPath->aLineBits[j].time = time;
+			pPath->aLineBits[j].position = position;
+			pPath->aLineBits[j].speed = FERRY_SPEED;
+			pPath->aLineBits[j].acceleration = 0.0f;
+			j++;
+			// distance to next keyframe
+			float dist = (stationDists[i] - FERRY_SLOWDOWN_DISTANCE) - position;
+			time += dist / FERRY_SPEED;
+			position += dist;
+
+			// Now slow down 50 units before stop
+			pPath->aLineBits[j].type = FERRY_SLOWING;
+			pPath->aLineBits[j].time = time;
+			pPath->aLineBits[j].position = position;
+			pPath->aLineBits[j].speed = FERRY_SPEED;
+			pPath->aLineBits[j].acceleration = -(FERRY_SPEED * FERRY_SPEED) / (4 * FERRY_SLOWDOWN_DISTANCE);
+			j++;
+			time += 2 * FERRY_SLOWDOWN_DISTANCE / FERRY_SPEED;
+			position += FERRY_SLOWDOWN_DISTANCE;	// at station
+
+			// stopping
+			pPath->aLineBits[j].type = FERRY_STOPPED;
+			pPath->aLineBits[j].time = time;
+			pPath->aLineBits[j].position = position;
+			pPath->aLineBits[j].speed = 0.0f;
+			pPath->aLineBits[j].acceleration = 0.0f;
+			j++;
+			time += FERRY_TIME_STOPPED_AT_STATION;
+
+			// accelerate again
+			pPath->aLineBits[j].type = FERRY_ACCELERATING;
+			pPath->aLineBits[j].time = time;
+			pPath->aLineBits[j].position = position;
+			pPath->aLineBits[j].speed = 0.0f;
+			pPath->aLineBits[j].acceleration = (FERRY_SPEED * FERRY_SPEED) / (4 * FERRY_SLOWDOWN_DISTANCE);
+			j++;
+			time += 2 * FERRY_SLOWDOWN_DISTANCE / FERRY_SPEED;
+			position += FERRY_SLOWDOWN_DISTANCE;	// after station
+		}
+		// last keyframe
+		pPath->aLineBits[j].type = FERRY_CRUISING;
+		pPath->aLineBits[j].time = time;
+		pPath->aLineBits[j].position = position;
+		pPath->aLineBits[j].speed = FERRY_SPEED;
+		pPath->aLineBits[j].acceleration = 0.0f;
+		j++;
+		pPath->TotalDurationOfTrack = time + (pPath->TotalLengthOfTrack - position) / FERRY_SPEED;
+
+		// end
+		pPath->aLineBits[j].time = pPath->TotalDurationOfTrack;
+	}
+}
+
+void CFerry::InitFerrys(void)
+{
+	printf("init ferrys\n");
+#ifdef GTA_NETWORK
+	if (gIsMultiplayerGame)
+		SetupForMultiplayer();
+#endif
+	if (!mspInst)
+		Init(nil);
+	for (int i = 0; i < NUM_FERRIES; i++)
+		mspInst->m_apFerries[i] = nil;
+	CStreaming::LoadAllRequestedModels(false);
+	CStreaming::RequestModel(MI_FERRY, 0);
+	CStreaming::LoadAllRequestedModels(false);
+}
+
+void CFerry::SwitchFerryCollision(int type)
+{
+	for (int i = 0; i < NUM_FERRIES; i++) {
+		CFerry* pFerry = GetFerry(i);
+		if (pFerry && pFerry->m_nCollision != type) {
+			pFerry->m_nCollision = type;
+			CVehicleModelInfo* mi = pFerry->GetModelInfo();
+			if (type == 1)
+				mi->SetColModel(&CTempColModels::ms_colModelFerryDocked);
+			else
+				mi->SetColModel(pFerry->m_pDefaultColModel, true);
+		}
+	}
+}
+
+void CFerry::UpdateFerrys(void)
+{
+	int i, j;
+	float t, deltaT;
+	if (mspInst->m_bFerriesDisabled)
+		return;
+	for (i = 0; i < NUM_FERRIES; i++) {
+		CFerry* pFerry = GetFerry(i);
+		if (pFerry) {
+			pFerry->m_nTimeAlongPath += CTimer::GetTimeStepInMilliseconds();
+			t = mspInst->pPathData[i/2]->TotalDurationOfTrack * (float)((pFerry->m_nTimeAlongPath + (i & 1) * 0x20000) & 0x3FFFF) / 0x40000;
+			// find current frame
+			for (j = 0; t > mspInst->pPathData[i / 2]->aLineBits[j + 1].time; j++);
+
+			deltaT = t - mspInst->pPathData[i / 2]->aLineBits[j].time;
+			if (pFerry->m_bFerryDocked) {
+				if (mspInst->pPathData[i / 2]->aLineBits[j].type == FERRY_SLOWING) {
+					pFerry->m_nTimeAlongPath += (mspInst->pPathData[i / 2]->aLineBits[j + 1].time - mspInst->pPathData[i / 2]->aLineBits[j].time);
+					j++;
+					if (j > NUM_FERRY_STATIONS * 4 + 1)
+						j = 0;
+					pFerry->m_bApproachingDock = true;
+					if ((i & 1) == 0) {
+						GetFerry(i + 1)->m_bApproachingDock = true;
+						GetFerry(i + 1)->m_nTimeAlongPath += (mspInst->pPathData[i / 2]->aLineBits[j + 1].time - mspInst->pPathData[i / 2]->aLineBits[j].time);
+					}
+					else {
+						GetFerry(i - 1)->m_bApproachingDock = true;
+						GetFerry(i - 1)->m_nTimeAlongPath += (mspInst->pPathData[i / 2]->aLineBits[j + 1].time - mspInst->pPathData[i / 2]->aLineBits[j].time);
+					}
+				}
+			}
+			if (pFerry->m_nSkipFerryStatus == 1) {
+				float fDelta = 0.0f;
+				pFerry->m_nSkipFerryStatus = 2;
+				while (mspInst->pPathData[i / 2]->aLineBits[j].type != FERRY_STOPPED) {
+					fDelta += (mspInst->pPathData[i / 2]->aLineBits[j + 1].time - mspInst->pPathData[i / 2]->aLineBits[j].time);
+					j++;
+					if (j > NUM_FERRY_STATIONS * 4)
+						j = 0;
+				}
+				pFerry->m_nTimeAlongPath += fDelta;
+				pFerry->m_bApproachingDock = true;
+				if ((i & 1) == 0) {
+					GetFerry(i + 1)->m_bApproachingDock = true;
+					GetFerry(i + 1)->m_nTimeAlongPath += fDelta;
+				}
+				else {
+					GetFerry(i - 1)->m_bApproachingDock = true;
+					GetFerry(i - 1)->m_nTimeAlongPath += fDelta;
+				}
+			}
+			switch (mspInst->pPathData[i / 2]->aLineBits[j].type) {
+			case FERRY_STOPPED:
+				mspInst->m_afPositions[i] = mspInst->pPathData[i / 2]->aLineBits[j].position;
+				mspInst->m_afSpeeds[i] = 0.0f;
+				break;
+			case FERRY_CRUISING:
+				mspInst->m_afPositions[i] = mspInst->pPathData[i / 2]->aLineBits[j].position + mspInst->pPathData[i / 2]->aLineBits[j].speed * deltaT;
+				mspInst->m_afSpeeds[i] = (mspInst->pPathData[i / 2]->TotalDurationOfTrack * 1000.0f / 0x40000) * mspInst->pPathData[i / 2]->aLineBits[j].speed;
+				break;
+			case FERRY_SLOWING:
+			case FERRY_ACCELERATING:
+				pFerry->m_bApproachingDock = (mspInst->pPathData[i / 2]->aLineBits[j].type == FERRY_SLOWING);
+				mspInst->m_afPositions[i] = mspInst->pPathData[i / 2]->aLineBits[j].position + (mspInst->pPathData[i / 2]->aLineBits[j].speed + mspInst->pPathData[i / 2]->aLineBits[j].acceleration * deltaT) * deltaT;
+				mspInst->m_afSpeeds[i] = (mspInst->pPathData[i / 2]->TotalDurationOfTrack * 1000.0f / 0x40000) * (mspInst->pPathData[i / 2]->aLineBits[j].speed + 2 * mspInst->pPathData[i / 2]->aLineBits[j].acceleration * deltaT);
+				break;
+			}
+		}
+	}
+}
+
+void CFerry::SetModelIndex(uint32 mi)
+{
+	CVehicle::SetModelIndex(mi);
+	for (int i = 0; i < NUM_FERRY_NODES; i++)
+		m_aFerryNodes[i] = nil;
+	CClumpModelInfo::FillFrameArray(GetClump(), m_aFerryNodes);
+}
+
+void CFerry::PreRender(void)
+{
+	CVehicleModelInfo* mi = GetModelInfo();
+	if (CGeneral::GetRandomTrueFalse())
+		CParticle::AddParticle(PARTICLE_FERRY_CHIM_SMOKE, GetMatrix() * mi->m_positions[FERRY_POS_CHIM_LEFT], CVector(0.0f, 0.0f, 0.2f));
+	CVector vVectorToCamera = GetPosition() - TheCamera.GetPosition();
+	CVector vDirectionToCamera;
+	float fDistanceToCamera = vVectorToCamera.Magnitude();
+	if (fDistanceToCamera == 0.0f)
+		vVectorToCamera = vDirectionToCamera = CVector(1.0f, 0.0f, 0.0f);
+	else
+		vDirectionToCamera = vVectorToCamera / fDistanceToCamera;
+	float dp = DotProduct(GetForward(), vDirectionToCamera);
+	if (dp < 0.0f) {
+		CVector vFrontLightPosition = GetMatrix() * mi->m_positions[FERRY_POS_LIGHT_FRONT];
+		CVector vFrontLightPosition2 = vFrontLightPosition - 2 * mi->m_positions[FERRY_POS_LIGHT_FRONT].x * GetRight();
+		float size = -dp + 1.0f;
+		float fIntensity = -dp * 0.4f + 0.2f;
+		if (dp < -0.9f && fDistanceToCamera < 35.0f) {
+			uint8 intensity = fIntensity * 255.0f;
+			CCoronas::RegisterCorona((uint32)(uintptr)this + 26, intensity, intensity, intensity, 255, vFrontLightPosition2, size, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+			CCoronas::RegisterCorona((uint32)(uintptr)this + 27, intensity, intensity, intensity, 255, vFrontLightPosition, size, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+		}
+		else {
+			uint8 intensity = fIntensity * 255.0f;
+			CCoronas::RegisterCorona((uint32)(uintptr)this + 26, intensity, intensity, intensity, 255, vFrontLightPosition2, size, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+			CCoronas::RegisterCorona((uint32)(uintptr)this + 27, intensity, intensity, intensity, 255, vFrontLightPosition, size, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+		}
+	}
+	CVector vRearLightPosition = GetMatrix() * mi->m_positions[FERRY_POS_LIGHT_REAR];
+	CVector vRearLightPosition2 = vRearLightPosition - 2 * mi->m_positions[FERRY_POS_LIGHT_REAR].x * GetRight();
+	CCoronas::RegisterCorona((uint32)(uintptr)this + 28, 255, 0, 0, 255, vRearLightPosition2, 1.0f, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+	CCoronas::RegisterCorona((uint32)(uintptr)this + 29, 255, 0, 0, 255, vRearLightPosition, 1.0f, 80.0f, CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
+}
+
+void CFerry::Render(void)
+{
+	m_bAlreadyRendered = true;
+	CEntity::Render();
+}
+
+void CFerry::RenderAllRemaning(void)
+{
+#ifdef GTA_NETWORK
+	if (gIsMultiplayerGame)
+		return;
+#endif
+	for (int i = 0; i < NUM_FERRIES; i++) {
+		CFerry* pFerry = GetFerry(i);
+		if (pFerry) {
+			if (!pFerry->m_bAlreadyRendered)
+				pFerry->Render();
+			pFerry->m_bAlreadyRendered = false;
+		}
+	}
+}
+
+void CFerry::FerryHitStuff(CPtrList& lst)
+{
+	for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) {
+		CPhysical* pEntity = (CPhysical*)pNode->item;
+		if (pEntity != this && Abs(GetPosition().x - pEntity->GetPosition().z) < 1.5f)
+			pEntity->bHitByTrain = true;
+	}
+}
+
+void CFerry::ProcessControl(void)
+{
+	if (gbModelViewer)
+		return;
+	PruneWakeTrail();
+	if (m_isFarAway && (m_nFerryId + CTimer::GetFrameCounter() & 0xF) != 0)
+		return;
+	CFerryPath* pPath = mspInst->pPathData[m_nFerryId / 2];
+	float fPosition = mspInst->m_afPositions[m_nFerryId];
+	float fSpeed = mspInst->m_afSpeeds[m_nFerryId];
+	if (fPosition < 0.0f)
+		fPosition += pPath->TotalLengthOfTrack;
+
+	CFerryNode* trackNodes = mspInst->pPathData[m_nFerryId / 2]->aTrackNodes;
+	int16 numTrackNodes = mspInst->pPathData[m_nFerryId / 2]->NumTrackNodes;
+	float totalLengthOfTrack = mspInst->pPathData[m_nFerryId / 2]->TotalLengthOfTrack;
+	float trackPosition = mspInst->m_afPositions[m_nFerryId];
+	float trackSpeed = mspInst->m_afSpeeds[m_nFerryId];
+
+	float trackPositionRear = trackPosition;
+	if (trackPositionRear < 0.0f)
+		trackPositionRear += totalLengthOfTrack;
+
+	// Advance current node to appropriate position
+	float pos1, pos2;
+	int nextTrackNode = m_nCurTrackNode + 1;
+	pos1 = trackNodes[m_nCurTrackNode].t;
+	if (nextTrackNode < numTrackNodes)
+		pos2 = trackNodes[nextTrackNode].t;
+	else {
+		nextTrackNode = 0;
+		pos2 = totalLengthOfTrack;
+	}
+	while (trackPositionRear < pos1 || trackPositionRear > pos2) {
+		m_nCurTrackNode = (m_nCurTrackNode + 1) % numTrackNodes;
+		nextTrackNode = m_nCurTrackNode + 1;
+		pos1 = trackNodes[m_nCurTrackNode].t;
+		if (nextTrackNode < numTrackNodes)
+			pos2 = trackNodes[nextTrackNode].t;
+		else {
+			nextTrackNode = 0;
+			pos2 = totalLengthOfTrack;
+		}
+	}
+	float dist = trackNodes[nextTrackNode].t - trackNodes[m_nCurTrackNode].t;
+	if (dist < 0.0f)
+		dist += totalLengthOfTrack;
+	float f = (trackPositionRear - trackNodes[m_nCurTrackNode].t) / dist;
+	CVector posRear = (1.0f - f) * CVector(trackNodes[m_nCurTrackNode].x, trackNodes[m_nCurTrackNode].y, trackNodes[m_nCurTrackNode].z) +
+		f * CVector(trackNodes[nextTrackNode].x, trackNodes[nextTrackNode].y, trackNodes[nextTrackNode].z);
+
+	// Now same again for the front
+	float trackPositionFront = trackPositionRear + 20.0f;
+	if (trackPositionFront > totalLengthOfTrack)
+		trackPositionFront -= totalLengthOfTrack;
+	int curTrackNodeFront = m_nCurTrackNode;
+	int nextTrackNodeFront = curTrackNodeFront + 1;
+	pos1 = trackNodes[curTrackNodeFront].t;
+	if (nextTrackNodeFront < numTrackNodes)
+		pos2 = trackNodes[nextTrackNodeFront].t;
+	else {
+		nextTrackNodeFront = 0;
+		pos2 = totalLengthOfTrack;
+	}
+	while (trackPositionFront < pos1 || trackPositionFront > pos2) {
+		curTrackNodeFront = (curTrackNodeFront + 1) % numTrackNodes;
+		nextTrackNodeFront = curTrackNodeFront + 1;
+		pos1 = trackNodes[curTrackNodeFront].t;
+		if (nextTrackNodeFront < numTrackNodes)
+			pos2 = trackNodes[nextTrackNodeFront].t;
+		else {
+			nextTrackNodeFront = 0;
+			pos2 = totalLengthOfTrack;
+		}
+	}
+	dist = trackNodes[nextTrackNodeFront].t - trackNodes[curTrackNodeFront].t;
+	if (dist < 0.0f)
+		dist += totalLengthOfTrack;
+	f = (trackPositionFront - trackNodes[curTrackNodeFront].t) / dist;
+	CVector posFront = (1.0f - f) * CVector(trackNodes[curTrackNodeFront].x, trackNodes[curTrackNodeFront].y, trackNodes[curTrackNodeFront].z) +
+		f * CVector(trackNodes[nextTrackNodeFront].x, trackNodes[nextTrackNodeFront].y, trackNodes[nextTrackNodeFront].z);
+
+	// Now set matrix
+	SetPosition((posRear + posFront) / 2.0f);
+	CVector fwd = posFront - posRear;
+	m_vecForwardSpeed = fwd;
+	fwd.Normalise();
+	float dp = DotProduct(fwd, GetForward());
+	if (Abs(dp) > 1.001f || Abs(dp) < 0.999f) {
+		CVector df = CrossProduct(fwd, GetForward());
+		CMatrix tmp;
+		float angle;
+		if (m_nSkipFerryStatus == 2) {
+			m_nSkipFerryStatus = 0;
+			angle = (fwd.x < 0.0f && GetForward().x < 0.0f) ? PI - Acos(dp) : Acos(dp);
+		}
+		else {
+			angle = 0.001f;
+		}
+		if (dp > 0.0f && df.z < 0.0f || dp <= 0.0f && df.z > 0.0f)
+			angle = -angle;
+		tmp.SetRotateZ(angle);
+		fwd = Multiply3x3(GetForward(), tmp);
+	}
+	else
+		fwd = GetForward();
+	CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
+	right.Normalise();
+	CVector up = CrossProduct(right, fwd);
+	GetRight() = right;
+	GetUp() = up;
+	GetForward() = fwd;
+
+	// Set speed
+	m_vecMoveSpeed = fwd * trackSpeed / 60.0f;
+	m_fSpeed = trackSpeed / 60.0f;
+	m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+
+	if (m_vecMoveSpeed.MagnitudeSqr() > 0.001f || !m_bApproachingDock) {
+		SetStatus(STATUS_FERRY_MOVING);
+		m_bFerryDocked = false;
+		m_bPlayerArrivedHorn = false;
+		m_nTimeLeftStation = CTimer::GetTimeInMilliseconds();
+	}
+	else {
+		SetStatus(STATUS_FERRY_NOT_MOVING);
+		PlayArrivedHorn();
+		m_bFerryDocked = true;
+	}
+
+	m_isFarAway = !((posFront - TheCamera.GetPosition()).Magnitude2D() < sq(1000.0f));
+
+	switch (m_nDoorState) {
+	case FERRY_DOOR_CLOSED:
+		break;
+	case FERRY_DOOR_OPENING:
+		if (CTimer::GetTimeInMilliseconds() < m_nDoorTimer) {
+			OpenFerryDoor(1.0f - (m_nDoorTimer - CTimer::GetTimeInMilliseconds()) / 1000.0f);
+		}
+		else {
+			OpenFerryDoor(1.0f);
+			m_nDoorState = FERRY_DOOR_OPEN;
+		}
+		break;
+
+	case FERRY_DOOR_OPEN:
+		break;
+
+	case FERRY_DOOR_CLOSING:
+		if (CTimer::GetTimeInMilliseconds() < m_nDoorTimer) {
+			OpenFerryDoor((m_nDoorTimer - CTimer::GetTimeInMilliseconds()) / 1000.0f);
+		}
+		else {
+			OpenFerryDoor(0.0f);
+			m_nDoorState = FERRY_DOOR_CLOSED;
+		}
+		break;
+	}
+
+	CVector wn = CWaterLevel::GetWaterNormal(GetPosition().x, GetPosition().y);
+	float fRollAngle = wn.x - GetUp().x;
+	if (fRollAngle < 0.0f)
+		fRollAngle = Max(-0.05f, fRollAngle);
+	else
+		fRollAngle = Min(0.05f, fRollAngle);
+
+	CVector oldPos = GetPosition();
+	SetPosition(-GetPosition());
+	CMatrix tmp2;
+	tmp2.SetRotateX(fRollAngle);
+	tmp2 = tmp2 * GetMatrix();
+	tmp2.SetTranslateOnly(oldPos);
+	GetMatrix() = tmp2;
+
+	GetMatrix().UpdateRW();
+	UpdateRwFrame();
+	RemoveAndAdd();
+
+	bIsStuck = false;
+	bIsInSafePosition = true;
+	bWasPostponed = false;
+
+	// request/remove model
+	if (m_isFarAway) {
+		if (m_rwObject)
+			DeleteRwObject();
+	}
+	else if (CStreaming::HasModelLoaded(MI_FERRY)) {
+		if (m_rwObject == nil) {
+			m_modelIndex = -1;
+			SetModelIndex(MI_FERRY);
+		}
+	}
+	else {
+		if (FindPlayerCoors().z * GetPosition().z >= 0.0f)
+			CStreaming::RequestModel(MI_FERRY, STREAMFLAGS_DEPENDENCY);
+	}
+
+	// Hit stuff
+	if (GetStatus() == STATUS_TRAIN_MOVING) {
+		CVector nfwd = GetForward() * GetColModel()->boundingBox.max.y;
+		if (m_vecForwardSpeed.x > 0.0f)
+			nfwd = -nfwd;
+		if ((m_nFerryId & 1) == 0)
+			nfwd = -nfwd;
+
+		int x, xmin, xmax;
+		int y, ymin, ymax;
+
+		CVector front = GetPosition() + nfwd + m_vecMoveSpeed * CTimer::GetTimeStep();
+		if (!m_isFarAway && m_vecMoveSpeed.Magnitude2D() > 0.05f)
+			AddWakePoint(nfwd * 0.85f + GetPosition());
+
+		xmin = CWorld::GetSectorIndexX(front.x - 3.0f);
+		if (xmin < 0) xmin = 0;
+		xmax = CWorld::GetSectorIndexX(front.x + 3.0f);
+		if (xmax > NUMSECTORS_X - 1) xmax = NUMSECTORS_X - 1;
+		ymin = CWorld::GetSectorIndexY(front.y - 3.0f);
+		if (ymin < 0) ymin = 0;
+		ymax = CWorld::GetSectorIndexY(front.y + 3.0f);
+		if (ymax > NUMSECTORS_Y - 1) ymax = NUMSECTORS_X - 1;
+
+		CWorld::AdvanceCurrentScanCode();
+
+		for (y = ymin; y <= ymax; y++)
+			for (x = xmin; x <= xmax; x++) {
+				CSector* s = CWorld::GetSector(x, y);
+				FerryHitStuff(s->m_lists[ENTITYLIST_VEHICLES]);
+				FerryHitStuff(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
+				FerryHitStuff(s->m_lists[ENTITYLIST_PEDS]);
+				FerryHitStuff(s->m_lists[ENTITYLIST_PEDS_OVERLAP]);
+			}
+	}
+}
+
+void CFerry::OpenFerryDoor(float ratio)
+{
+	if (!m_rwObject)
+		return;
+
+	int door1 = 0;
+	int door2 = 1;
+	if (!m_bUseFrontDoor) {
+		door1 = 2;
+		door2 = 3;
+	}
+	int node1 = m_bUseFrontDoor ? FERRY_DOOR_FRONT : FERRY_DOOR_BACK;
+	int node2 = m_bUseFrontDoor ? FERRY_RAMP_FRONT : FERRY_RAMP_BACK;
+	CMatrix doorL(RwFrameGetMatrix(m_aFerryNodes[node1]));
+	CMatrix doorR(RwFrameGetMatrix(m_aFerryNodes[node2]));
+	CVector posL = doorL.GetPosition();
+	CVector posR = doorR.GetPosition();
+
+	bool isClosed = m_doors[0].IsClosed();	// useless
+
+	m_doors[door1].Open(ratio);
+	m_doors[door2].Open(ratio);
+
+	doorL.SetRotateXOnly(m_doors[door1].m_fAngle);
+	doorR.SetRotateXOnly(m_doors[door2].m_fAngle);
+
+	doorL.UpdateRW();
+	doorR.UpdateRW();
+}
+
+CVector CFerry::GetBoardingSpace(CFerry::eSpaceUse use, CFerry::eSpaceStyle style, uint8 position)
+{
+	CVehicleModelInfo* pModelInfo = GetModelInfo();
+	CVector space;
+	if (m_nFerryId & 1) {
+		if (style == FERRY_SPACE_STYLE_0)
+			style = FERRY_SPACE_STYLE_1;
+		else
+			style = FERRY_SPACE_STYLE_0;
+	}
+	switch (use) {
+	case FERRY_SPACE_PED:
+		space = pModelInfo->m_positions[FERRY_POS_PED_POINT];
+		break;
+	case FERRY_SPACE_CAR:
+		space = pModelInfo->m_positions[FERRY_POS_CAR1 + position];
+		break;
+	}
+	switch (style) {
+	case FERRY_SPACE_STYLE_0:
+		space = GetMatrix() * space;
+		break;
+	case FERRY_SPACE_STYLE_1:
+		space = GetMatrix() * space - (2 * space.x) * GetRight() - (2 * space.y) * GetForward();
+		break;
+	}
+	return space;
+}
+
+CFerry* CFerry::GetClosestFerry(float x, float y)
+{
+	int closest = -1;
+	float mindist = 9999.9f;
+	for (int i = 0; i < NUM_FERRIES; i++) {
+		CFerry* pFerry = GetFerry(i);
+		if (pFerry) {
+			float dist = ((CVector2D)pFerry->GetPosition() - CVector2D(x, y)).Magnitude();
+			if (dist < 300.0f && dist < mindist) {
+				mindist = dist;
+				closest = i;
+			}
+		}
+	}
+	if (closest == -1)
+		return nil;
+	return GetFerry(closest);
+}
+
+bool CFerry::IsDocked(void)
+{
+	return m_bFerryDocked;
+}
+
+void CFerry::OpenDoor(void)
+{
+	printf("opening the ferry door\n");
+	m_nDoorState = FERRY_DOOR_OPENING;
+	m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 10000;
+	CVehicleModelInfo* pModelInfo = GetModelInfo();
+	CVector2D vPlayerPos = FindPlayerPed()->GetPosition();
+	float fDistToCar4 = (vPlayerPos - GetMatrix() * pModelInfo->m_positions[FERRY_POS_CAR4]).Magnitude();
+	float fDistToCar1 = (vPlayerPos - GetMatrix() * pModelInfo->m_positions[FERRY_POS_CAR1]).Magnitude();
+	m_bUseFrontDoor = true;
+	if (fDistToCar4 < fDistToCar1)
+		m_bUseFrontDoor = false;
+	// AudioManager.DirectlyEnqueueSample(0xb8,0,0,1,0x5622,0x7f,0x14,0); // TODO
+}
+
+void CFerry::CloseDoor(void)
+{
+	printf("closing the ferry door\n");
+	m_nDoorState = FERRY_DOOR_CLOSING;
+	m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 10000;
+	// AudioManager.DirectlyEnqueueSample(0xb8, 0, 0, 1, 0x5622, 0x7f, 0x14, 0); // TODO
+}
+
+bool CFerry::IsDoorOpen(void)
+{
+	return m_nDoorState == FERRY_DOOR_OPEN;
+}
+
+bool CFerry::IsDoorClosed(void)
+{
+	return m_nDoorState == FERRY_DOOR_CLOSED;
+}
+
+void CFerry::CompleteDorrMovement(void)
+{
+	m_nDoorTimer = 0;
+}
+
+void CFerry::DissableFerryPath(int)
+{
+}
+
+void CFerry::EnableFerryPath(int path)
+{
+	CStreaming::LoadAllRequestedModels(false);
+	CStreaming::RequestModel(MI_FERRY, 0);
+	CStreaming::LoadAllRequestedModels(false);
+	for (int i = path * 2; i < path * 2 + 2; i++) {
+		CFerry* pFerry = new CFerry(MI_FERRY, PERMANENT_VEHICLE);
+		bool bDirect = i & 1;
+		mspInst->m_apFerries[i] = pFerry;
+		pFerry->SetPosition(0.0f, 0.0f, 0.0f);
+		pFerry->SetStatus(STATUS_ABANDONED);
+		pFerry->bIsLocked = true;
+		pFerry->SetHeading(bDirect ? HALFPI : 3 * HALFPI);
+		pFerry->m_nFerryId = i;
+		pFerry->m_nCurTrackNode = 0;
+		CWorld::Add(pFerry);
+	}
+}
+
+void CFerry::SkipFerryToNextDock(void)
+{
+	m_nSkipFerryStatus = 1;
+}
+
+void CFerry::PruneWakeTrail(void)
+{
+	int16 num_remaining = 0;
+	for (int i = 0; i < NUM_WAKE_POINTS; i++) {
+		if (mspInst->m_afWakePointTimer[m_nFerryId][i] <= 0.0f)
+			break;
+		if (mspInst->m_afWakePointTimer[m_nFerryId][i] <= CTimer::GetTimeStep()) {
+			mspInst->m_afWakePointTimer[m_nFerryId][i] = 0.0f;
+			break;
+		}
+		mspInst->m_afWakePointTimer[m_nFerryId][i] -= CTimer::GetTimeStep();
+		num_remaining++;
+	}
+	mspInst->m_anNumWakePoints[m_nFerryId] = num_remaining;
+}
+
+void CFerry::AddWakePoint(CVector point)
+{
+	if (mspInst->m_afWakePointTimer[m_nFerryId][0] > 0.0f) {
+		int nNewWakePoints = Min(NUM_WAKE_POINTS - 1, mspInst->m_anNumWakePoints[m_nFerryId]);
+		for (int i = 0; i < nNewWakePoints; i++) {
+			mspInst->m_avWakePoints[m_nFerryId][i + 1] = mspInst->m_avWakePoints[m_nFerryId][i];
+			mspInst->m_afWakePointTimer[m_nFerryId][i + 1] = mspInst->m_afWakePointTimer[m_nFerryId][i];
+		}
+	}
+	mspInst->m_avWakePoints[m_nFerryId][0] = point;
+	mspInst->m_afWakePointTimer[m_nFerryId][0] = 900.0f; // TODO: define
+	mspInst->m_anNumWakePoints[m_nFerryId] += 1;
+}
+
+void CFerry::PlayArrivedHorn(void)
+{
+	if (m_bPlayerArrivedHorn)
+		return;
+	m_bPlayerArrivedHorn = true;
+	float fDistToCamera = (GetPosition() - TheCamera.GetPosition()).Magnitude();
+	if (fDistToCamera < 200.0f) {
+		uint8 volume = (200.0f - fDistToCamera) / 200.0f * 127;
+		// AudioManager.DirectlyEnqueueSample(0x32, 0, 0, 1, 18000, volume, 0x32, 0); // TODO
+	}
+}
+
+CVector CFerry::GetNearestDoor(CVector)
+{
+	return CVector(0.0f, 0.0f, 1.0f);
+}
+
+void CFerry::SetupForMultiplayer(void)
+{
+	for (int i = 0; i < NUM_FERRIES; i++)
+		mspInst->m_apFerries[i] = nil;
+	printf("setting up the ferrys for multiplayer\n");
+	CStreaming::SetModelIsDeletable(MI_FERRY);
+}
+
+void CFerry::Write(base::cRelocatableChunkWriter& writer)
+{
+	writer.AllocateRaw(mspInst, sizeof(*mspInst), 4);
+	if (!mspInst)
+		return;
+	for (int i = 0; i < NUM_FERRY_PATHS; i++) {
+		writer.AllocateRaw(mspInst->pPathData[i], sizeof(*mspInst->pPathData[i]), 4);
+		writer.AddPatch(&mspInst->pPathData[i]);
+		writer.AllocateRaw(mspInst->pPathData[i]->aTrackNodes, mspInst->pPathData[i]->NumTrackNodes * sizeof(CFerryNode), 4);
+		writer.AddPatch(&mspInst->pPathData[i]->aTrackNodes);
+		writer.AllocateRaw(mspInst->pPathData[i]->aLineBits, (NUM_FERRY_STATIONS * 4 + 2) * sizeof(CFerryInterpolationLine), 4);
+		writer.AddPatch(&mspInst->pPathData[i]->aTrackNodes);
+	}
+}
+
diff --git a/src/vehicles/Ferry.h b/src/vehicles/Ferry.h
index 375dfce1..f7936da9 100644
--- a/src/vehicles/Ferry.h
+++ b/src/vehicles/Ferry.h
@@ -11,3 +11,133 @@ enum eFerryNodes
 	FERRY_RAMP_BACK,
 	NUM_FERRY_NODES
 };
+
+enum {
+	NUM_FERRY_STATIONS = 2,
+	NUM_FERRIES = NUM_FERRY_PATHS * 2,
+	NUM_WAKE_POINTS = 64
+};
+
+enum {
+	FERRY_STOPPED = 0,
+	FERRY_CRUISING,
+	FERRY_SLOWING,
+	FERRY_ACCELERATING
+};
+
+enum
+{
+	FERRY_DOOR_CLOSED = 0,
+	FERRY_DOOR_OPENING,
+	FERRY_DOOR_OPEN,
+	FERRY_DOOR_CLOSING
+};
+
+struct CFerryNode
+{
+	float x;
+	float y;
+	float z;
+	float t;
+};
+
+struct CFerryInterpolationLine
+{
+	uint8 type;
+	float time;	// when does this keyframe start
+	// initial values at start of frame
+	float position;
+	float speed;
+	float acceleration;
+};
+
+struct CFerryPath
+{
+	float TotalLengthOfTrack;
+	float TotalDurationOfTrack;
+	int16 NumTrackNodes;
+	CFerryNode* aTrackNodes;
+	CFerryInterpolationLine* aLineBits;
+};
+
+class CFerry;
+
+class CFerryInst
+{
+public:
+	CFerryPath* pPathData[NUM_FERRY_PATHS];
+	float m_afPositions[NUM_FERRIES];
+	float m_afSpeeds[NUM_FERRIES];
+	CFerry* m_apFerries[NUM_FERRIES];
+	bool m_bFerriesDisabled;
+	uint16 m_anNumWakePoints[NUM_FERRIES];
+	CVector2D m_avWakePoints[NUM_FERRIES][NUM_WAKE_POINTS];
+	float m_afWakePointTimer[NUM_FERRIES][NUM_WAKE_POINTS];
+};
+
+class CFerry : public CVehicle
+{
+public:
+	int16 m_nFerryId;
+	uint16 m_isFarAway;
+	uint16 m_nCurTrackNode;
+	float m_fSpeed;
+	bool m_bFerryDocked;
+	uint32 m_nDoorTimer;
+	uint32 m_nTimeLeftStation;
+	int16 m_nDoorState;
+	bool m_bApproachingDock;
+	uint8 m_nSkipFerryStatus;
+	uint32 m_nTimeAlongPath;
+	bool m_bUseFrontDoor;
+	CVector m_vecForwardSpeed;
+	CColModel* m_pDefaultColModel;
+	uint8 m_nCollision;
+	CDoor m_doors[4];
+	RwFrame* m_aFerryNodes[NUM_FERRY_NODES];
+	bool m_bAlreadyRendered;
+	bool m_bPlayerArrivedHorn;
+
+	static CFerryInst* mspInst;
+
+	enum eSpaceUse {
+		FERRY_SPACE_PED = 0,
+		FERRY_SPACE_CAR
+	};
+	enum eSpaceStyle {
+		FERRY_SPACE_STYLE_0 = 0,
+		FERRY_SPACE_STYLE_1
+	};
+	void Render(void);
+	static void EnableFerryPath(int);
+	CFerry(int32, uint8);
+	void SetModelIndex(uint32);
+	static void InitFerrys(void);
+	static void Init(void*);
+	void ProcessControl(void);
+	void PlayArrivedHorn(void);
+	void AddWakePoint(CVector);
+	void PruneWakeTrail(void);
+	void SkipFerryToNextDock(void);
+	static void DissableFerryPath(int);
+	void CompleteDorrMovement(void);
+	bool IsDoorOpen(void);
+	void CloseDoor(void);
+	bool IsDocked(void);
+	static CFerry* GetClosestFerry(float, float);
+	CVector GetBoardingSpace(CFerry::eSpaceUse, CFerry::eSpaceStyle, uint8);
+	CVector GetNearestDoor(CVector);
+	void OpenFerryDoor(float);
+	void FerryHitStuff(CPtrList&);
+	static void RenderAllRemaning(void);
+	static void UpdateFerrys(void);
+	static void SwitchFerryCollision(int);
+	void SetupForMultiplayer(void);
+	void Write(base::cRelocatableChunkWriter&);
+	virtual void OpenDoor(void);
+	void PreRender(void);
+	virtual bool IsDoorClosed(void);
+
+	static CFerry* GetFerry(int i) { return mspInst ? mspInst->m_apFerries[i] : nil; }
+	static void SetFerriesDisabled(bool disabled) { mspInst->m_bFerriesDisabled = disabled; }
+};
diff --git a/src/vehicles/Train.cpp b/src/vehicles/Train.cpp
index 55be4a20..3c3d45d7 100644
--- a/src/vehicles/Train.cpp
+++ b/src/vehicles/Train.cpp
@@ -15,6 +15,10 @@
 #include "Train.h"
 #include "AudioScriptObject.h"
 
+#define TRAIN_SPEED (15.0f)
+#define TRAIN_SLOWDOWN_DISTANCE (40.0f)
+#define TRAIN_TIME_STOPPED_AT_STATION (25.0f)
+
 static CTrainNode* pTrackNodes;
 static int16 NumTrackNodes;
 static float StationDist[3] = { 873.0f, 1522.0f, 2481.0f };
@@ -598,23 +602,23 @@ CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int1
 		interpLines[j].type = 1;
 		interpLines[j].time = time;
 		interpLines[j].position = position;
-		interpLines[j].speed = 15.0f;
+		interpLines[j].speed = TRAIN_SPEED;
 		interpLines[j].acceleration = 0.0f;
 		j++;
 		// distance to next keyframe
-		float dist = (stationDists[i]-40.0f) - position;
-		time += dist/15.0f;
+		float dist = (stationDists[i]-TRAIN_SLOWDOWN_DISTANCE) - position;
+		time += dist/TRAIN_SPEED;
 		position += dist;
 
 		// Now slow down 40 units before stop
 		interpLines[j].type = 2;
 		interpLines[j].time = time;
 		interpLines[j].position = position;
-		interpLines[j].speed = 15.0f;
-		interpLines[j].acceleration = -45.0f/32.0f;
+		interpLines[j].speed = TRAIN_SPEED;
+		interpLines[j].acceleration = -(TRAIN_SPEED * TRAIN_SPEED) / (4 * TRAIN_SLOWDOWN_DISTANCE);
 		j++;
-		time += 80.0f/15.0f;
-		position += 40.0f;	// at station
+		time += 2*TRAIN_SLOWDOWN_DISTANCE/TRAIN_SPEED;
+		position += TRAIN_SLOWDOWN_DISTANCE;	// at station
 
 		// stopping
 		interpLines[j].type = 0;
@@ -623,26 +627,26 @@ CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int1
 		interpLines[j].speed = 0.0f;
 		interpLines[j].acceleration = 0.0f;
 		j++;
-		time += 25.0f;
+		time += TRAIN_TIME_STOPPED_AT_STATION;
 
 		// accelerate again
 		interpLines[j].type = 2;
 		interpLines[j].time = time;
 		interpLines[j].position = position;
 		interpLines[j].speed = 0.0f;
-		interpLines[j].acceleration = 45.0f/32.0f;
+		interpLines[j].acceleration = (TRAIN_SPEED * TRAIN_SPEED) / (4 * TRAIN_SLOWDOWN_DISTANCE);
 		j++;
-		time += 80.0f/15.0f;
-		position += 40.0f;	// after station
+		time += 2*TRAIN_SLOWDOWN_DISTANCE /TRAIN_SPEED;
+		position += TRAIN_SLOWDOWN_DISTANCE;	// after station
 	}
 	// last keyframe
 	interpLines[j].type = 1;
 	interpLines[j].time = time;
 	interpLines[j].position = position;
-	interpLines[j].speed = 15.0f;
+	interpLines[j].speed = TRAIN_SPEED;
 	interpLines[j].acceleration = 0.0f;
 	j++;
-	*totalDuration = time + (*totalLength - position)/15.0f;
+	*totalDuration = time + (*totalLength - position)/ TRAIN_SPEED;
 
 	// end
 	interpLines[j].time = *totalDuration;