diff --git a/src/core/common.h b/src/core/common.h
index e5702a2f..4b7bcb0a 100644
--- a/src/core/common.h
+++ b/src/core/common.h
@@ -139,6 +139,7 @@ inline float sq(float x) { return x*x; }
 #define SQR(x) ((x) * (x))
 
 #define PI M_PI
+#define TWOPI PI*2
 #define DEGTORAD(x) ((x) * PI / 180.0f)
 #define RADTODEG(x) ((x) * 180.0f / PI)
 
diff --git a/src/render/Particle.h b/src/render/Particle.h
index 25677569..eaacf2f5 100644
--- a/src/render/Particle.h
+++ b/src/render/Particle.h
@@ -6,13 +6,13 @@ class CEntity;
 
 class CParticle
 {
+public:
 	enum
 	{
 		RAND_TABLE_SIZE    = 20,
 		SIN_COS_TABLE_SIZE = 1024
 	};
-	
-public:
+
 	CVector   m_vecPosition;
 	CVector   m_vecVelocity;
 	CVector   m_vecScreenPosition;
diff --git a/src/render/RenderBuffer.cpp b/src/render/RenderBuffer.cpp
index 9a1ed58d..1b91156e 100644
--- a/src/render/RenderBuffer.cpp
+++ b/src/render/RenderBuffer.cpp
@@ -5,7 +5,7 @@
 int32 &TempBufferVerticesStored = *(int32*)0x8F5F78;
 int32 &TempBufferIndicesStored = *(int32*)0x8F1A4C;
 
-RwIm3DVertex *TempVertexBuffer = (RwIm3DVertex*)0x862330;
+RwIm3DVertex *TempBufferRenderVertices = (RwIm3DVertex*)0x862330;
 RwImVertexIndex *TempBufferRenderIndexList = (RwImVertexIndex*)0x846288;
 
 int RenderBuffer::VerticesToBeStored;
@@ -21,12 +21,12 @@ RenderBuffer::ClearRenderBuffer(void)
 void
 RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart)
 {
-	if(TempBufferIndicesStored + numIndices >= 1024)
+	if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE)
 		RenderStuffInBuffer();
-	if(TempBufferVerticesStored + numVertices >= 256)
+	if(TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE)
 		RenderStuffInBuffer();
         *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored];
-        *vertexStart = &TempVertexBuffer[TempBufferVerticesStored];
+        *vertexStart = &TempBufferRenderVertices[TempBufferVerticesStored];
         IndicesToBeStored = numIndices;
         VerticesToBeStored = numVertices;
 }
@@ -44,7 +44,7 @@ RenderBuffer::StopStoring(void)
 void
 RenderBuffer::RenderStuffInBuffer(void)
 {
-	if(TempBufferVerticesStored && RwIm3DTransform(TempVertexBuffer, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
+	if(TempBufferVerticesStored && RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
 		RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
 		RwIm3DEnd();
 	}
diff --git a/src/render/RenderBuffer.h b/src/render/RenderBuffer.h
index 66baa2d0..28bfe157 100644
--- a/src/render/RenderBuffer.h
+++ b/src/render/RenderBuffer.h
@@ -8,3 +8,11 @@ public:
 	static void StopStoring(void);
 	static void RenderStuffInBuffer(void);
 };
+
+#define TEMPBUFFERVERTSIZE 256
+#define TEMPBUFFERINDEXSIZE 1024
+
+extern int32 &TempBufferVerticesStored;
+extern int32 &TempBufferIndicesStored;
+extern RwIm3DVertex *TempBufferRenderVertices;
+extern RwImVertexIndex *TempBufferRenderIndexList;
\ No newline at end of file
diff --git a/src/render/WaterLevel.cpp b/src/render/WaterLevel.cpp
index 866847b3..d5138b28 100644
--- a/src/render/WaterLevel.cpp
+++ b/src/render/WaterLevel.cpp
@@ -1,5 +1,1255 @@
 #include "common.h"
-#include "patcher.h"
+#include "FileMgr.h"
+#include "TxdStore.h"
+#include "Timer.h"
+#include "Weather.h"
+#include "Camera.h"
+#include "Vehicle.h"
+#include "Boat.h"
+#include "World.h"
+#include "General.h"
+#include "TimeCycle.h"
+#include "ZoneCull.h"
+#include "Clock.h"
+#include "Particle.h"
+#include "ParticleMgr.h"
+#include "RwHelper.h"
+#include "Streaming.h"
+#include "CdStream.h"
+#include "Pad.h"
+#include "RenderBuffer.h"
+#include <rpworld.h>
 #include "WaterLevel.h"
+#include "patcher.h"
 
-WRAPPER void CWaterLevel::RenderWater(void) { EAXJMP(0x5554E0); }
+float TEXTURE_ADDU;
+float TEXTURE_ADDV;
+
+int32 CWaterLevel::ms_nNoOfWaterLevels;
+float CWaterLevel::ms_aWaterZs[48];
+CRect CWaterLevel::ms_aWaterRects[48];
+uint8 CWaterLevel::aWaterBlockList[WATER_BLOCK_SIZE][WATER_BLOCK_SIZE];
+uint8 CWaterLevel::aWaterFineBlockList[WATER_FINEBLOCK_SIZE][WATER_FINEBLOCK_SIZE];
+bool CWaterLevel::WavesCalculatedThisFrame;
+RpAtomic *CWaterLevel::ms_pWavyAtomic;
+RpGeometry *CWaterLevel::apGeomArray[8];
+int16 CWaterLevel::nGeomUsed;
+
+//RwTexture *gpWaterTex;
+//RwRaster *gpWaterRaster;
+
+RwTexture *&gpWaterTex = *(RwTexture **)0x64D070;
+RwRaster *&gpWaterRaster = *(RwRaster **)0x8F5FD4;
+
+
+const float fAdd1 = 180.0f;
+const float fAdd2 = 80.0f;
+const float fRedMult = 0.6f;
+const float fGreenMult = 1.0f;
+const float fBlueMult = 1.4f;
+
+
+
+void
+CWaterLevel::Initialise(char *pWaterDat)
+{
+	ms_nNoOfWaterLevels = 0;
+	
+	int32 hFile = -1;
+	
+	do
+	{
+		hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb");
+	}
+	while ( hFile < 0 );
+	
+	if ( hFile > 0 )
+	{
+		if ( hFile >= 0 )
+		{
+			CFileMgr::Read(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels));
+			CFileMgr::Read(hFile, (char *)ms_aWaterZs,	sizeof(ms_aWaterZs));
+			CFileMgr::Read(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects));
+			CFileMgr::Read(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList));
+			CFileMgr::Read(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList));
+		}
+		
+		CFileMgr::CloseFile(hFile);
+	}
+	
+	CTxdStore::PushCurrentTxd();
+
+	int32 slot = CTxdStore::FindTxdSlot("particle");
+	CTxdStore::SetCurrentTxd(slot);
+	
+	if ( gpWaterTex == NULL )
+		gpWaterTex = RwTextureRead("water_old", NULL);
+	gpWaterRaster = RwTextureGetRaster(gpWaterTex);
+	
+	CTxdStore::PopCurrentTxd();
+
+	CreateWavyAtomic();
+	FreeBoatWakeArray();
+	
+	printf("Done Initing waterlevels\n");
+}
+
+void
+CWaterLevel::Shutdown()
+{
+	FreeBoatWakeArray();
+	DestroyWavyAtomic();
+
+	if ( gpWaterTex != NULL )
+	{
+		RwTextureDestroy(gpWaterTex);
+		gpWaterTex = NULL;
+	}
+}
+
+void
+CWaterLevel::CreateWavyAtomic()
+{
+	RpGeometry *wavyGeometry;
+	RpMaterial *wavyMaterial;
+	RpTriangle *wavyTriangles;
+	RpMorphTarget *wavyMorphTarget;
+	RwSphere boundingSphere;	
+	RwV3d *wavyVert;
+
+	RwFrame *wavyFrame;
+	
+	{
+		wavyGeometry = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP
+													|rpGEOMETRYTEXTURED
+													|rpGEOMETRYPRELIT
+													|rpGEOMETRYMODULATEMATERIALCOLOR);
+													
+		ASSERT(wavyGeometry != NULL);
+	
+	}
+	
+	{
+		wavyMaterial = RpMaterialCreate();
+		
+		ASSERT(wavyMaterial != NULL);
+		ASSERT(gpWaterTex   != NULL);
+		
+		RpMaterialSetTexture(wavyMaterial, gpWaterTex);
+	}
+	
+	{
+		wavyTriangles = RpGeometryGetTriangles(wavyGeometry);
+		
+		ASSERT(wavyTriangles != NULL);
+		/*  
+			[B]       [C]
+			 ***********
+			 *       * *
+			 *     *   *
+			 *   *     *
+			 * *       *
+			 ***********
+			[A]       [D]
+		*/
+		
+		for ( int32 i = 0; i < 8; i++ )
+		{
+			for ( int32 j = 0; j < 8; j++ )
+			{	
+				RpGeometryTriangleSetVertexIndices(wavyGeometry, 
+						&wavyTriangles[2 * 8*i + 2*j + 0], /*A*/9*i+j+0, /*B*/9*i+j+1,   /*C*/9*i+j+9+1);
+                                                                  
+				RpGeometryTriangleSetVertexIndices(wavyGeometry,  
+						&wavyTriangles[2 * 8*i + 2*j + 1], /*A*/9*i+j+0, /*C*/9*i+j+9+1, /*D*/9*i+j+9  );
+	
+				RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 0], wavyMaterial);
+				RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 1], wavyMaterial);
+			}
+		}
+	}
+	
+
+	{
+		wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0);
+		wavyVert = RpMorphTargetGetVertices(wavyMorphTarget);
+		
+		ASSERT(wavyMorphTarget != NULL);
+		ASSERT(wavyVert != NULL);
+		
+		for ( int32 i = 0; i < 9; i++ )
+		{
+			for ( int32 j = 0; j < 9; j++ )
+			{
+				wavyVert[9*i+j].x = (float)i * 4.0f;
+				wavyVert[9*i+j].y = (float)j * 4.0f;
+				wavyVert[9*i+j].z = 0.0f;
+			}
+		}
+		
+		RpMorphTargetCalcBoundingSphere(wavyMorphTarget, &boundingSphere);
+		RpMorphTargetSetBoundingSphere(wavyMorphTarget, &boundingSphere);
+		RpGeometryUnlock(wavyGeometry);
+	}
+	
+	
+	{
+		wavyFrame = RwFrameCreate();
+		ASSERT( wavyFrame != NULL );
+		
+		ms_pWavyAtomic = RpAtomicCreate();		
+		ASSERT( ms_pWavyAtomic != NULL );
+		
+		RpAtomicSetGeometry(ms_pWavyAtomic, wavyGeometry, 0);
+		RpAtomicSetFrame(ms_pWavyAtomic, wavyFrame);
+		RpMaterialDestroy(wavyMaterial);
+		RpGeometryDestroy(wavyGeometry);
+	}
+}
+
+void
+CWaterLevel::DestroyWavyAtomic()
+{
+	RwFrame *frame;
+	
+	frame = RpAtomicGetFrame(ms_pWavyAtomic);
+	
+	RpAtomicDestroy(ms_pWavyAtomic);
+	
+	RwFrameDestroy(frame);	
+}
+
+bool
+CWaterLevel::GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ)
+{
+	int32 x = WATER_HUGE_X(fX);
+	int32 y = WATER_HUGE_Y(fY);
+	
+	ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE );
+	ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE );
+
+	uint8 nBlock = aWaterFineBlockList[x][y];
+
+	if ( nBlock == 128 )
+		return false;
+
+	ASSERT( pfOutLevel != NULL );
+	*pfOutLevel = ms_aWaterZs[nBlock];
+
+	float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f);
+	
+	float fWave = sin
+	(
+		/*( WATER_UNSIGN_Y(fY) - float(y) * MAX_HUGE_SECTORS + WATER_UNSIGN_X(fX) - float(x) * MAX_HUGE_SECTORS )*/ // VC
+		(float)( ((int32)fX & (MAX_HUGE_SECTORS-1)) + ((int32)fY & (MAX_HUGE_SECTORS-1)) )
+		* (TWOPI / MAX_HUGE_SECTORS ) + fAngle
+	);
+
+	float fWindFactor = CWeather::Wind * 0.7f + 0.3f;
+	
+	*pfOutLevel += fWave * fWindFactor;
+
+	if ( bDontCheckZ == false && (*pfOutLevel - fZ) > 3.0f )
+	{
+		*pfOutLevel = 0.0f;
+		return false;
+	}
+
+	return true;
+}
+
+bool
+CWaterLevel::GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel)
+{
+	int32 x = WATER_HUGE_X(fX);
+	int32 y = WATER_HUGE_Y(fY);
+	
+	ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE );
+	ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE );
+	
+	uint8 nBlock = aWaterFineBlockList[x][y];
+		
+	if ( nBlock == 128 )
+		return false;
+	
+	ASSERT( pfOutLevel != NULL );
+	*pfOutLevel = ms_aWaterZs[nBlock];
+
+	return true;
+}
+
+inline float
+_GetWaterDrawDist()
+{
+	// if z less then 15.0f return 1200.0f 
+	if ( TheCamera.GetPosition().z < 15.0f )
+		return 1200.0f;
+
+	// if z greater then 60.0f return 2000.0f;
+	if ( TheCamera.GetPosition().z > 60.0f )
+		return 2000.0f;
+
+	return (TheCamera.GetPosition().z + -15.0f) * 800.0f / 45.0f + 1200.0f;
+}
+
+inline float
+_GetWavyDrawDist()
+{
+	if ( FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() )
+		return 120.0f;
+	else
+		return 70.0f;
+}
+
+inline void
+_GetCamBounds(bool *bUseCamStartY, bool *bUseCamEndY, bool *bUseCamStartX, bool *bUseCamEndX)
+{
+	if ( TheCamera.GetForward().z > -0.8f )
+	{
+		if ( fabsf(TheCamera.GetForward().x) > fabsf(TheCamera.GetForward().y) )
+		{
+			if ( TheCamera.GetForward().x > 0.0f )
+				*bUseCamStartX = true;
+			else
+				*bUseCamEndX = true;
+		}
+		else
+		{
+			if ( TheCamera.GetForward().y > 0.0f )
+				*bUseCamStartY = true;
+			else
+				*bUseCamEndY = true;
+		}
+	}
+}
+
+float
+SectorRadius(float fSize)
+{
+	return sqrtf(powf(fSize, 2) + powf(fSize, 2));
+}
+
+void
+CWaterLevel::RenderWater()
+{
+	bool bUseCamEndX   = false;
+	bool bUseCamStartY = false;
+	
+	bool bUseCamStartX = false;
+	bool bUseCamEndY   = false;
+	
+	float fWavySectorMaxRenderDist      = _GetWavyDrawDist();
+	float fWavySectorMaxRenderDistSqr   = SQR(fWavySectorMaxRenderDist);
+	
+	_GetCamBounds(&bUseCamStartY, &bUseCamEndY, &bUseCamStartX, &bUseCamEndX);
+
+	float fHugeSectorMaxRenderDist    = _GetWaterDrawDist();
+	float fHugeSectorMaxRenderDistSqr = SQR(fHugeSectorMaxRenderDist);
+	
+	float windAddUV = CWeather::Wind * 0.0015f + 0.0005f;
+	
+	
+	if ( !CTimer::GetIsPaused() )
+	{
+		TEXTURE_ADDU += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV;
+		TEXTURE_ADDV += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV;
+	}
+	
+	if ( TEXTURE_ADDU >= 1.0f )
+		TEXTURE_ADDU = 0.0f;
+	if ( TEXTURE_ADDV >= 1.0f )
+		TEXTURE_ADDV = 0.0f;
+	
+	WavesCalculatedThisFrame = false;
+
+	RwRGBA color = { 0, 0, 0, 255 };
+	
+	color.red   = uint32((CTimeCycle::GetDirectionalRed()   * 0.5f + CTimeCycle::GetAmbientRed()  ) * 255.0f);
+	color.green = uint32((CTimeCycle::GetDirectionalGreen() * 0.5f + CTimeCycle::GetAmbientGreen()) * 255.0f);
+	color.blue  = uint32((CTimeCycle::GetDirectionalBlue()  * 0.5f + CTimeCycle::GetAmbientBlue() ) * 255.0f);
+
+	TempBufferVerticesStored = 0;
+	TempBufferIndicesStored = 0;
+	
+	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster);
+	RwRenderStateSet(rwRENDERSTATEFOGENABLE,     (void *)TRUE);
+	RwRenderStateSet(rwRENDERSTATESRCBLEND,      (void *)rwBLENDONE);
+	RwRenderStateSet(rwRENDERSTATEDESTBLEND,     (void *)rwBLENDZERO);
+	
+	CVector2D camPos
+	(
+		TheCamera.GetPosition().x,
+		TheCamera.GetPosition().y
+	);
+
+	int32 nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x - fHugeSectorMaxRenderDist);
+	int32 nEndX   = WATER_TO_HUGE_SECTOR_X(camPos.x + fHugeSectorMaxRenderDist) + 1;
+	int32 nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y - fHugeSectorMaxRenderDist);
+	int32 nEndY   = WATER_TO_HUGE_SECTOR_Y(camPos.y + fHugeSectorMaxRenderDist) + 1;
+
+	if ( bUseCamStartX )
+		nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x);
+	if ( bUseCamEndX )
+		nEndX   = WATER_TO_HUGE_SECTOR_X(camPos.x);
+	if ( bUseCamStartY )
+		nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y);
+	if ( bUseCamEndY )                             
+		nEndY   = WATER_TO_HUGE_SECTOR_Y(camPos.y);
+
+	nStartX = clamp(nStartX, 0, MAX_HUGE_SECTORS - 1);
+	nEndX   = clamp(nEndX,   0, MAX_HUGE_SECTORS - 1);
+	nStartY = clamp(nStartY, 0, MAX_HUGE_SECTORS - 1);
+	nEndY   = clamp(nEndY,   0, MAX_HUGE_SECTORS - 1);
+	
+	for ( int32 x = nStartX; x <= nEndX; x++ )
+	{
+		for ( int32 y = nStartY; y <= nEndY; y++ )
+		{
+			if (   !(aWaterBlockList[2*x+0][2*y+0] & 128)
+				|| !(aWaterBlockList[2*x+1][2*y+0] & 128)
+				|| !(aWaterBlockList[2*x+0][2*y+1] & 128)
+				|| !(aWaterBlockList[2*x+1][2*y+1] & 128) )
+			{
+				float fX = WATER_FROM_HUGE_SECTOR_X(x);
+				float fY = WATER_FROM_HUGE_SECTOR_Y(y);
+
+				CVector2D vecHugeSectorCentre
+				(
+					fX + HUGE_SECTOR_SIZE/2,
+					fY + HUGE_SECTOR_SIZE/2
+				);
+
+				float fHugeSectorDistToCamSqr = (camPos - vecHugeSectorCentre).MagnitudeSqr();
+
+				if ( fHugeSectorMaxRenderDistSqr > fHugeSectorDistToCamSqr )
+				{
+					if ( TheCamera.IsSphereVisible(CVector(vecHugeSectorCentre.x, vecHugeSectorCentre.y, 0.0f), SectorRadius(HUGE_SECTOR_SIZE),
+							&CMatrix(TheCamera.GetCameraMatrix())) )
+					{
+						if ( fHugeSectorDistToCamSqr >= SQR(500.0f) /*fHugeSectorNearDist*/ )
+						{
+							float fZ;
+	
+							if ( !(aWaterBlockList[2*x+0][2*y+0] & 128) )
+								fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+0] ];
+	
+							if ( !(aWaterBlockList[2*x+1][2*y+0] & 128) )
+								fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+0] ];
+	
+							if ( !(aWaterBlockList[2*x+0][2*y+1] & 128) )
+								fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+1] ];
+	
+							if ( !(aWaterBlockList[2*x+1][2*y+1] & 128) )
+								fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+1] ];
+							
+							RenderOneFlatHugeWaterPoly(fX, fY, fZ, color);
+						}
+						else
+						{
+							for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ )
+							{
+								for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ )
+								{
+									if ( !(aWaterBlockList[x2][y2] & 128) )
+									{
+										float fLargeX = WATER_FROM_LARGE_SECTOR_X(x2);
+										float fLargeY = WATER_FROM_LARGE_SECTOR_Y(y2);
+						
+										CVector2D vecLargeSectorCentre
+										(
+											fLargeX + LARGE_SECTOR_SIZE/2,
+											fLargeY + LARGE_SECTOR_SIZE/2
+										);
+										
+										float fLargeSectorDistToCamSqr = (camPos - vecLargeSectorCentre).MagnitudeSqr();
+										
+										if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr )
+										{
+											if ( TheCamera.IsSphereVisible(CVector(vecLargeSectorCentre.x, vecLargeSectorCentre.y, 0.0f), SectorRadius(LARGE_SECTOR_SIZE), //90.879997f,
+												&CMatrix(TheCamera.GetCameraMatrix())) )
+											{
+												// Render four small(32x32) sectors, or one large(64x64).
+
+												//														
+												//	       [N]
+												//	    ---------
+												//	    |0x1|1x1|
+												//	[W] --------- [E]
+												//	    |0x0|1x0|
+												//	    ---------
+												//	       [S]
+												//	
+
+												if ( fLargeSectorDistToCamSqr < SQR(176.0f) )
+												{						
+													float fZ;
+													
+													// WS
+													if ( !(aWaterFineBlockList[2*x2+0][2*y2+0] & 128) )
+													{
+														float fSmallX = fLargeX;
+														float fSmallY = fLargeY;
+														
+														CVector2D vecSmallSectorCentre
+														(
+															fSmallX + SMALL_SECTOR_SIZE/2,
+															fSmallY + SMALL_SECTOR_SIZE/2
+														);
+														
+														float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr();
+														fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+0] ];
+														
+														if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr )
+															RenderOneWavySector(fSmallX, fSmallY, fZ, color);
+														else
+															RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color);
+													}
+													
+													// SE
+													if ( !(aWaterFineBlockList[2*x2+1][2*y2+0] & 128) )
+													{
+														float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2);
+														float fSmallY = fLargeY;
+														
+														CVector2D vecSmallSectorCentre
+														(
+															fSmallX + SMALL_SECTOR_SIZE/2,
+															fSmallY + SMALL_SECTOR_SIZE/2
+														);
+														
+														float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr();
+														fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+0] ];
+														
+														if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr )
+															RenderOneWavySector(fSmallX, fSmallY, fZ, color);
+														else
+															RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color);
+													}
+													
+													// WN
+													if ( !(aWaterFineBlockList[2*x2+0][2*y2+1] & 128) )
+													{
+														float fSmallX = fLargeX;
+														float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2);
+														
+														CVector2D vecSmallSectorCentre
+														(
+															fSmallX + SMALL_SECTOR_SIZE/2,
+															fSmallY + SMALL_SECTOR_SIZE/2
+														);
+														
+														float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr();
+														fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+1] ];
+														
+														if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr )
+															RenderOneWavySector(fSmallX, fSmallY, fZ, color);
+														else
+															RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color);
+													}
+													
+													//NE
+													if ( !(aWaterFineBlockList[2*x2+1][2*y2+1] & 128) )
+													{
+														float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2);
+														float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2);
+														
+														CVector2D vecSmallSectorCentre
+														(
+															fSmallX + SMALL_SECTOR_SIZE/2,
+															fSmallY + SMALL_SECTOR_SIZE/2
+														);
+														
+														float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr();
+														fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+1] ];
+														
+														if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr )
+															RenderOneWavySector(fSmallX, fSmallY, fZ, color);
+														else
+															RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color);
+													}
+												}
+												else
+												{
+													float fZ;
+                                                
+													fZ = ms_aWaterZs[ aWaterBlockList[x2][y2] ];
+                                                
+													RenderOneFlatLargeWaterPoly(fLargeX, fLargeY, fZ, color);
+												}
+											}	//	if ( TheCamera.IsSphereVisible
+										}	//	if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr )
+									}	//	if ( !(aWaterBlockList[x2][y2] & 128) )
+								}	//	for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ )
+							}	//	for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ )
+							//
+							
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	/*
+	 -----------      ----------------------       ----------------------
+	|    [N]    |    |       [ EndY ]       |     |       [  top ]       |
+	|           |    |                      |     |                      |
+	|[W] [0] [E]|    |[StartX]  []  [ EndX ]|     |[ left ]  []  [ right]|
+	|           |    |                      |     |                      |
+	|    [S]    |    |       [StartY]       |     |       [bottom]       |
+	 -----------      ----------------------       ----------------------
+	
+	
+	[S] [StartY]  [bottom]
+	[N] [EndY]    [top]
+	[W] [StartX]  [left]
+	[E] [EndX]    [right]
+	
+	[S]    -> [N] && [W]  -> [E]
+	bottom -> top && left -> right
+	*/
+
+	if ( !bUseCamStartY )
+	{
+		for ( int32 x = 0; x < 26; x++ )
+		{
+			for ( int32 y = 0; y < 5; y++ )
+			{
+				float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f;
+				float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f;
+				
+				CVector2D vecExtraHugeSectorCentre
+				(
+					fX + EXTRAHUGE_SECTOR_SIZE/2,
+					fY + EXTRAHUGE_SECTOR_SIZE/2
+				);
+				
+				float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude();
+				
+				if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr )
+				{
+					if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE),
+							&CMatrix(TheCamera.GetCameraMatrix())) )
+					{
+						RenderOneFlatExtraHugeWaterPoly(
+							vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2,
+							vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2,
+							0.0f,
+							color);
+					}
+				}
+			}
+		}
+	}
+	
+	for ( int32 y = 5; y < 21; y++ )
+	{
+		for ( int32 x = 0; x < 5; x++ )
+		{
+			float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f;
+			float fX2 = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f;
+			float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f;
+			
+			if ( !bUseCamStartX )
+			{
+				CVector2D vecExtraHugeSectorCentre
+				(
+					fX + EXTRAHUGE_SECTOR_SIZE/2,
+					fY + EXTRAHUGE_SECTOR_SIZE/2
+				);
+				
+				float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude();
+				
+				if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr )
+				{
+					if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE),
+							&CMatrix(TheCamera.GetCameraMatrix())) )
+					{
+						RenderOneFlatExtraHugeWaterPoly(
+							vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2,
+							vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2,
+							0.0f,
+							color);
+					}
+				}
+			}
+			
+			if ( !bUseCamEndX )
+			{
+				CVector2D vecExtraHugeSectorCentre
+				(
+					-(fX2 + EXTRAHUGE_SECTOR_SIZE/2),
+					fY + EXTRAHUGE_SECTOR_SIZE/2
+				);
+				
+				float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude();
+				
+				if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr )
+				{
+					if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE),
+							&CMatrix(TheCamera.GetCameraMatrix())) )
+					{
+						RenderOneFlatExtraHugeWaterPoly(
+							vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2,
+							vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2,
+							0.0f,
+							color);
+					}
+				}
+			}
+		}
+	}
+
+	RenderAndEmptyRenderBuffer();
+	
+	CVector cur_pos = TheCamera.GetPosition();
+	
+	if ( !CCullZones::CamNoRain()
+		&& !CCullZones::PlayerNoRain()
+		&& CWeather::NewWeatherType == WEATHER_SUNNY
+		&& CClock::GetHours() > 6 && CClock::GetHours() < 20
+		&& WavesCalculatedThisFrame)
+	{
+		static CVector prev_pos(0.0f, 0.0f, 0.0f);
+		static CVector prev_front(0.0f, 0.0f, 0.0f);
+		static int32 timecounter;
+
+		if ( fabs(prev_pos.x - cur_pos.x) + fabs(prev_pos.y - cur_pos.y) + fabs(prev_pos.z - cur_pos.z) > 1.5f )
+		{
+			prev_pos = cur_pos;
+			timecounter = CTimer::GetTimeInMilliseconds();
+		}
+		else if ( CTimer::GetTimeInMilliseconds() - timecounter > 5000 )
+		{
+			static int32 birdgenTime = 0;
+			
+			if ( CTimer::GetTimeInMilliseconds() - birdgenTime > 1000 )
+			{
+				birdgenTime = CTimer::GetTimeInMilliseconds();
+				
+				CVector vecPos = cur_pos;
+				
+				float fAngle = CGeneral::GetRandomNumberInRange(90.0f, 150.0f);
+				
+				int32 nSinCosIdx = CGeneral::GetRandomNumber() % CParticle::SIN_COS_TABLE_SIZE-1;
+				
+				float fCos = CParticle::m_CosTable[nSinCosIdx];
+				float fSin = CParticle::m_SinTable[nSinCosIdx];
+
+				vecPos.x += (fCos - fSin) * fAngle;
+				vecPos.y += (fSin + fCos) * fAngle;
+				vecPos.z += CGeneral::GetRandomNumberInRange(10.0f, 30.0f);
+				
+				CVector vecDir(CGeneral::GetRandomNumberInRange(-1.0f, 1.0f),
+								CGeneral::GetRandomNumberInRange(-1.0f, 1.0f),
+								0.0f);
+				
+				CParticle::AddParticle(PARTICLE_BIRD_FRONT, vecPos, vecDir);
+			}
+		}
+	}
+	
+	DefinedState();
+}
+
+void
+CWaterLevel::RenderOneFlatSmallWaterPoly(float fX, float fY, float fZ, RwRGBA const &color)
+{
+	if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 )
+		RenderAndEmptyRenderBuffer();
+	
+	int32 vidx = TempBufferVerticesStored;
+	
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 1], fX, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 1.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 2], fX + SMALL_SECTOR_SIZE, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 1.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 1.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 3], fX + SMALL_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 1.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha);
+	
+	
+	int32 iidx = TempBufferIndicesStored;
+
+	TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2;
+	TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1;
+	TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3;
+	TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2;
+	
+	TempBufferVerticesStored += 4;
+	TempBufferIndicesStored += 6;
+}
+
+void
+CWaterLevel::RenderOneFlatLargeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color)
+{
+	if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 )
+		RenderAndEmptyRenderBuffer();
+	
+	int32 vidx = TempBufferVerticesStored;
+	
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 1], fX, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 2.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 2], fX + LARGE_SECTOR_SIZE, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 2.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 2.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 3], fX + LARGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 2.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha);
+
+
+	int32 iidx = TempBufferIndicesStored;
+
+	TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2;
+	TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1;
+	TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3;
+	TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2;
+	
+	TempBufferVerticesStored += 4;
+	TempBufferIndicesStored += 6;
+}
+
+void
+CWaterLevel::RenderOneFlatHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color)
+{
+	if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 )
+		RenderAndEmptyRenderBuffer();
+	
+	int32 vidx = TempBufferVerticesStored;
+
+	
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 1], fX, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 4.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 2], fX + HUGE_SECTOR_SIZE, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 4.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 4.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 3], fX + HUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 4.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255);
+
+
+	int32 iidx = TempBufferIndicesStored;
+
+	TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2;
+	TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1;
+	TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3;
+	TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2;
+	
+	TempBufferVerticesStored += 4;
+	TempBufferIndicesStored += 6;
+}
+
+void
+CWaterLevel::RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color)
+{
+	if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 )
+		RenderAndEmptyRenderBuffer();
+	
+	int32 vidx = TempBufferVerticesStored;
+
+	
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 1], fX, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 8.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255);
+
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 2], fX + EXTRAHUGE_SECTOR_SIZE, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 8.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 8.0f);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255);
+	
+	RwIm3DVertexSetPos  (&TempBufferRenderVertices[vidx + 3], fX + EXTRAHUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET);
+	RwIm3DVertexSetU    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 8.0f);
+	RwIm3DVertexSetV    (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV);
+	RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255);
+
+
+	int32 iidx = TempBufferIndicesStored;
+
+	TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2;
+	TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1;
+	TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0;
+	TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3;
+	TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2;
+	
+	TempBufferVerticesStored += 4;
+	TempBufferIndicesStored += 6;
+}
+
+void
+CWaterLevel::RenderOneWavySector(float fX, float fY, float fZ, RwRGBA const &color, bool bUnk)
+{
+	float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f);	
+	
+	if ( !WavesCalculatedThisFrame )
+	{
+		nGeomUsed = 0;
+		
+		WavesCalculatedThisFrame = true;
+		
+		CBoat::FillBoatList();
+		
+		ASSERT( ms_pWavyAtomic != NULL );
+
+		RpGeometry *geometry = RpAtomicGetGeometry(ms_pWavyAtomic);
+		
+		ASSERT( geometry != NULL );
+
+		RwRGBA      *wavyPreLights = RpGeometryGetPreLightColors(geometry);
+		RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(geometry, rwTEXTURECOORDINATEINDEX0);
+		RwV3d       *wavyVertices  = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geometry, 0));
+		
+		ASSERT( wavyPreLights != NULL );
+		ASSERT( wavyTexCoords != NULL );
+		ASSERT( wavyVertices  != NULL );
+
+		RpGeometryLock(geometry, rpGEOMETRYLOCKVERTICES
+								| rpGEOMETRYLOCKPRELIGHT
+								| rpGEOMETRYLOCKTEXCOORDS);
+		
+		for ( int32 i = 0; i < 9; i++ )
+		{
+			for ( int32 j = 0; j < 9; j++ )
+			{
+				wavyTexCoords[9*i+j].u = float(i) / 8 + TEXTURE_ADDV;
+				wavyTexCoords[9*i+j].v = float(j) / 8 + TEXTURE_ADDU;
+				RwRGBAAssign(&wavyPreLights[9*i+j], &color);
+
+				wavyVertices[9*i+j].z = ( CWeather::Wind * 0.7f + 0.3f )
+										* ( sinf(float(i + j) * DEGTORAD(45.0f) + fAngle) )
+										+ ( CWeather::Wind * 0.2f * sinf(float(j - i) * PI + (2.0f * fAngle)) );
+			}
+		}
+		
+		RpGeometryUnlock(geometry);
+	}
+	
+	static CBoat *apBoatList[4] = { NULL };
+	
+	if ( apGeomArray[0]
+		&& nGeomUsed < MAX_BOAT_WAKES
+		&& CBoat::IsSectorAffectedByWake(
+			CVector2D(fX + (SMALL_SECTOR_SIZE / 2), fY + (SMALL_SECTOR_SIZE / 2)),
+			SMALL_SECTOR_SIZE / 2,
+			apBoatList) )
+	{
+		float fWakeColor = fAdd1 - max(255.0f - float(color.blue + color.red + color.green) / 3, fAdd2);
+		
+		RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic);	
+		RpGeometry *geom  = apGeomArray[nGeomUsed++];
+		
+		ASSERT( wavyGeometry != NULL );
+		ASSERT( geom != NULL );
+		
+		RpAtomic *atomic = RpAtomicCreate();
+		ASSERT( atomic != NULL );
+		
+		RpAtomicSetGeometry(atomic, geom, 0);
+		
+		RwFrame *frame = RwFrameCreate();
+		ASSERT( frame != NULL );
+		
+		RwMatrixCopy(RwFrameGetMatrix(frame), RwFrameGetMatrix(RpAtomicGetFrame(ms_pWavyAtomic)));
+		RpAtomicSetFrame(atomic, frame);
+		
+		RwTexCoords *geomTexCoords  = RpGeometryGetVertexTexCoords(geom, rwTEXTURECOORDINATEINDEX0);
+		RwTexCoords *wavyTexCoord   = RpGeometryGetVertexTexCoords(wavyGeometry, rwTEXTURECOORDINATEINDEX0);
+		RwRGBA *geomPreLights       = RpGeometryGetPreLightColors(geom);
+		RwV3d  *geomVertices        = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geom, 0));
+		RwV3d  *wavyVertices        = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(wavyGeometry, 0));
+		
+		ASSERT( geomTexCoords != NULL );
+		ASSERT( wavyTexCoord  != NULL );
+		ASSERT( geomPreLights != NULL );
+		ASSERT( geomVertices  != NULL );
+		ASSERT( wavyVertices  != NULL );
+
+		RpGeometryLock(geom, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKPRELIGHT | rpGEOMETRYLOCKTEXCOORDS);
+		
+		for ( int32 i = 0; i < 9; i++ )
+		{
+			for ( int32 j = 0; j < 9; j++ )
+			{
+				geomTexCoords[9*i+j] = wavyTexCoord[9*i+j];
+
+				float fVertexX = (float)i * 4.0f + fX;
+				float fVertexY = (float)j * 4.0f + fY;
+
+				float fDistMult = 0.0f;
+		
+				for ( int32 k = 0; k < 4; k++ )
+				{
+					if ( apBoatList[k] != NULL )
+						fDistMult += CBoat::IsVertexAffectedByWake(CVector(fVertexX, fVertexY, 0.0f), apBoatList[k]);
+				}
+				
+				if ( fDistMult > 0.0f )
+				{
+					RwRGBA wakeColor;
+					
+					RwRGBAAssign(&wakeColor, &color);
+
+					wakeColor.red   = min(color.red   + int32(fWakeColor * fRedMult   * fDistMult), 255);
+					wakeColor.green = min(color.green + int32(fWakeColor * fGreenMult * fDistMult), 255);
+					wakeColor.blue  = min(color.blue  + int32(fWakeColor * fBlueMult  * fDistMult), 255);
+					
+					RwRGBAAssign(&geomPreLights[9*i+j], &wakeColor);
+
+				}
+				else
+					RwRGBAAssign(&geomPreLights[9*i+j], &color);
+				
+				
+				geomVertices[9*i+j].z = wavyVertices[9*i+j].z;
+			}
+		}
+		
+		RpGeometryUnlock(geom);
+		
+		
+		RwV3d pos = {0.0f, 0.0f, 0.0f};
+
+		pos.x = fX;
+		pos.z = fZ;
+		pos.y = fY;
+
+		RwFrameTranslate(RpAtomicGetFrame(atomic), &pos, rwCOMBINEREPLACE);
+
+		RpAtomicRender(atomic);
+		
+		RpAtomicDestroy(atomic);
+		RwFrameDestroy(frame);
+	}
+	else
+	{
+		RwV3d pos = { 0.0f, 0.0f, 0.0f };
+
+		pos.x = fX;
+		pos.y = fY;
+		pos.z = fZ;
+		
+		ASSERT( ms_pWavyAtomic != NULL );
+		
+		RwFrameTranslate(RpAtomicGetFrame(ms_pWavyAtomic), &pos, rwCOMBINEREPLACE);
+		
+		RpAtomicRender(ms_pWavyAtomic);
+	}
+}
+
+float
+CWaterLevel::CalcDistanceToWater(float fX, float fY)
+{
+	const float fSectorMaxRenderDist = 75.0f;
+	
+	int32 nStartX = WATER_TO_SMALL_SECTOR_X(fX - fSectorMaxRenderDist) - 1;
+	int32 nEndX   = WATER_TO_SMALL_SECTOR_X(fX + fSectorMaxRenderDist) + 1;
+	int32 nStartY = WATER_TO_SMALL_SECTOR_Y(fY - fSectorMaxRenderDist) - 1;
+	int32 nEndY   = WATER_TO_SMALL_SECTOR_Y(fY + fSectorMaxRenderDist) + 1;
+	
+	nStartX = clamp(nStartX, 0, MAX_SMALL_SECTORS - 1);
+	nEndX   = clamp(nEndX,   0, MAX_SMALL_SECTORS - 1);
+	nStartY = clamp(nStartY, 0, MAX_SMALL_SECTORS - 1);
+	nEndY   = clamp(nEndY,   0, MAX_SMALL_SECTORS - 1);
+	
+	float fDistSqr = 1.0e10f;
+	
+	for ( int32 x = nStartX; x <= nEndX; x++ )
+	{
+		for ( int32 y = nStartY; y <= nEndY; y++ )
+		{
+			if ( !(aWaterFineBlockList[x][y] & 128) )
+			{				
+				float fSectorX = WATER_FROM_SMALL_SECTOR_X(x);
+				float fSectorY = WATER_FROM_SMALL_SECTOR_Y(y);
+				
+				CVector2D vecDist
+				(
+					fSectorX + SMALL_SECTOR_SIZE - fX,
+					fSectorY + SMALL_SECTOR_SIZE - fY
+				);
+				
+				fDistSqr = min(vecDist.MagnitudeSqr(), fDistSqr);
+			}
+		}
+	}
+
+	return clamp(sqrt(fDistSqr) - 23.0f, 0.0f, fSectorMaxRenderDist);
+}
+
+void
+CWaterLevel::RenderAndEmptyRenderBuffer()
+{
+	if ( TempBufferVerticesStored )
+	{
+		LittleTest();
+
+		if ( RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, NULL, rwIM3D_VERTEXUV) )
+		{
+			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
+			RwIm3DEnd();
+		}
+	}
+	
+	TempBufferIndicesStored = 0;
+	TempBufferVerticesStored = 0;
+}
+
+void
+CWaterLevel::AllocateBoatWakeArray()
+{
+	CStreaming::MakeSpaceFor(14 * CDSTREAM_SECTOR_SIZE); 
+
+	ASSERT(ms_pWavyAtomic != NULL );
+	
+	RpGeometry    *wavyGeometry    = RpAtomicGetGeometry(ms_pWavyAtomic);
+	RpMorphTarget *wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0);
+	RpMaterial    *wavyMaterial    = RpGeometryGetMaterial(wavyGeometry, 0);
+
+	ASSERT(wavyGeometry    != NULL );
+	ASSERT(wavyMorphTarget != NULL );
+	ASSERT(wavyMaterial    != NULL );
+
+	for ( int32 geom = 0; geom < MAX_BOAT_WAKES; geom++ )
+	{
+		if ( apGeomArray[geom] == NULL )
+		{
+			apGeomArray[geom] = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP
+															| rpGEOMETRYPRELIT
+															| rpGEOMETRYMODULATEMATERIALCOLOR
+															| rpGEOMETRYTEXTURED);
+			ASSERT(apGeomArray[geom] != NULL);
+
+			RpTriangle *geomTriangles = RpGeometryGetTriangles(apGeomArray[geom]);
+			
+			ASSERT( geomTriangles != NULL );
+
+			for ( int32 i = 0; i < 8; i++ )
+			{
+				for ( int32 j = 0; j < 8; j++ )
+				{
+					
+					/*  
+						[B]       [C]
+						 ***********
+						 *       * *
+						 *     *   *
+						 *   *     *
+						 * *       *
+						 ***********
+						[A]       [D]
+					*/
+					
+					
+					RpGeometryTriangleSetVertexIndices(apGeomArray[geom],
+						&geomTriangles[2 * 8*i + 2*j + 0], /*A*/i*9+j+0, /*B*/i*9+j+1,   /*C*/i*9+j+9+1);
+					
+					RpGeometryTriangleSetVertexIndices(apGeomArray[geom],
+						&geomTriangles[2 * 8*i + 2*j + 1], /*A*/i*9+j+0, /*C*/i*9+j+9+1, /*D*/i*9+j+9  );
+
+					RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 0], wavyMaterial);
+					
+					RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 1], wavyMaterial);
+				}
+			}
+
+			RpMorphTarget *geomMorphTarget = RpGeometryGetMorphTarget(apGeomArray[geom], 0);
+			RwV3d         *geomVertices    = RpMorphTargetGetVertices(geomMorphTarget);
+			
+			ASSERT( geomMorphTarget != NULL );
+			ASSERT( geomVertices != NULL );
+
+			for ( int32 i = 0; i < 9; i++ )
+			{
+				for ( int32 j = 0; j < 9; j++ )
+				{
+					geomVertices[9*i+j].x = (float)i * 4.0f;
+					geomVertices[9*i+j].y = (float)j * 4.0f;
+					geomVertices[9*i+j].z = 0.0f;
+				}
+			}
+
+			RpMorphTargetSetBoundingSphere(geomMorphTarget, RpMorphTargetGetBoundingSphere(wavyMorphTarget));			
+			RpGeometryUnlock(apGeomArray[geom]);
+		}
+	}
+}
+
+void
+CWaterLevel::FreeBoatWakeArray()
+{
+	for ( int32 i = 0; i < MAX_BOAT_WAKES; i++ )
+	{
+		if ( apGeomArray[i] != NULL )
+		{
+			RpGeometryDestroy(apGeomArray[i]);
+			apGeomArray[i] = NULL;
+		}
+	}
+	
+	nGeomUsed = 0;  
+}
+
+STARTPATCHES
+	InjectHook(0x554EA0, &CWaterLevel::Initialise, PATCH_JUMP);
+	InjectHook(0x554FE0, &CWaterLevel::Shutdown, PATCH_JUMP);
+	InjectHook(0x555010, &CWaterLevel::CreateWavyAtomic, PATCH_JUMP);
+	InjectHook(0x5552A0, &CWaterLevel::DestroyWavyAtomic, PATCH_JUMP);
+	InjectHook(0x5552C0, &CWaterLevel::GetWaterLevel, PATCH_JUMP);
+	InjectHook(0x555440, &CWaterLevel::GetWaterLevelNoWaves, PATCH_JUMP);
+	InjectHook(0x5554E0, &CWaterLevel::RenderWater, PATCH_JUMP);
+	InjectHook(0x556C30, &CWaterLevel::RenderOneFlatSmallWaterPoly, PATCH_JUMP);
+	InjectHook(0x556E80, &CWaterLevel::RenderOneFlatLargeWaterPoly, PATCH_JUMP);
+	InjectHook(0x5570D0, &CWaterLevel::RenderOneFlatHugeWaterPoly, PATCH_JUMP);
+	InjectHook(0x557320, &CWaterLevel::RenderOneFlatExtraHugeWaterPoly, PATCH_JUMP);
+	InjectHook(0x557570, &CWaterLevel::RenderOneWavySector, PATCH_JUMP);
+	InjectHook(0x557C30, &CWaterLevel::CalcDistanceToWater, PATCH_JUMP);
+	InjectHook(0x557EA0, &CWaterLevel::RenderAndEmptyRenderBuffer, PATCH_JUMP);
+	InjectHook(0x557F00, &CWaterLevel::AllocateBoatWakeArray, PATCH_JUMP);
+	InjectHook(0x5581C0, &CWaterLevel::FreeBoatWakeArray, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/WaterLevel.h b/src/render/WaterLevel.h
index 70a2ba97..b8ec7a4d 100644
--- a/src/render/WaterLevel.h
+++ b/src/render/WaterLevel.h
@@ -1,7 +1,96 @@
 #pragma once
 
+#define WATER_BLOCK_SIZE     LARGE_SECTOR_SIZE
+#define WATER_FINEBLOCK_SIZE HUGE_SECTOR_SIZE
+#define WATER_Z_OFFSET (1.5f)
+
+#define MAX_SMALL_SECTORS      128
+#define MAX_LARGE_SECTORS      64
+#define MAX_HUGE_SECTORS       32
+#define MAX_EXTRAHUGE_SECTORS  16
+
+#define SMALL_SECTOR_SIZE      32
+#define LARGE_SECTOR_SIZE      64
+#define HUGE_SECTOR_SIZE       128
+#define EXTRAHUGE_SECTOR_SIZE  256
+
+#define WATER_START_X -2048.0f
+#define WATER_END_X    2048.0f
+
+#define WATER_START_Y -2048.0f
+#define WATER_END_Y    2048.0f
+
+#define WATER_WIDTH		((WATER_END_X - WATER_START_X))
+#define WATER_HEIGHT	((WATER_END_Y - WATER_START_Y))
+
+
+#define WATER_UNSIGN_X(x)                   ( (x) + (WATER_WIDTH /2) )
+#define WATER_UNSIGN_Y(y)                   ( (y) + (WATER_HEIGHT/2) )
+#define WATER_SIGN_X(x)                     ( (x) - (WATER_WIDTH /2) )
+#define WATER_SIGN_Y(y)                     ( (y) - (WATER_HEIGHT/2) )
+
+// 32
+#define WATER_SMALL_X(x)                    ( WATER_UNSIGN_X(x)					/ MAX_SMALL_SECTORS     )
+#define WATER_SMALL_Y(y)                    ( WATER_UNSIGN_Y(y)					/ MAX_SMALL_SECTORS     )
+#define WATER_FROM_SMALL_SECTOR_X(x)        ( ((x) - (MAX_SMALL_SECTORS/2)    ) * SMALL_SECTOR_SIZE     )
+#define WATER_FROM_SMALL_SECTOR_Y(y)        ( ((y) - (MAX_SMALL_SECTORS/2)    ) * SMALL_SECTOR_SIZE     )
+#define WATER_TO_SMALL_SECTOR_X(x)          ( WATER_UNSIGN_X(x)					/ SMALL_SECTOR_SIZE     )
+#define WATER_TO_SMALL_SECTOR_Y(y)          ( WATER_UNSIGN_Y(y)					/ SMALL_SECTOR_SIZE     )
+			
+// 64			
+#define WATER_LARGE_X(x)                    ( WATER_UNSIGN_X(x)					/ MAX_LARGE_SECTORS     )
+#define WATER_LARGE_Y(y)                    ( WATER_UNSIGN_Y(y)					/ MAX_LARGE_SECTORS     )
+#define WATER_FROM_LARGE_SECTOR_X(x)        ( ((x) - (MAX_LARGE_SECTORS/2)    ) * LARGE_SECTOR_SIZE     )
+#define WATER_FROM_LARGE_SECTOR_Y(y)        ( ((y) - (MAX_LARGE_SECTORS/2)    ) * LARGE_SECTOR_SIZE     )
+#define WATER_TO_LARGE_SECTOR_X(x)          ( WATER_UNSIGN_X(x)					/ LARGE_SECTOR_SIZE     )
+#define WATER_TO_LARGE_SECTOR_Y(y)          ( WATER_UNSIGN_Y(y)					/ LARGE_SECTOR_SIZE     )
+				
+// 128				
+#define WATER_HUGE_X(x)                     ( WATER_UNSIGN_X(x)					/ MAX_HUGE_SECTORS      )
+#define WATER_HUGE_Y(y)                     ( WATER_UNSIGN_Y(y)					/ MAX_HUGE_SECTORS      )
+#define WATER_FROM_HUGE_SECTOR_X(x)         ( ((x) - (MAX_HUGE_SECTORS/2)     ) * HUGE_SECTOR_SIZE      )
+#define WATER_FROM_HUGE_SECTOR_Y(y)         ( ((y) - (MAX_HUGE_SECTORS/2)     ) * HUGE_SECTOR_SIZE      )
+#define WATER_TO_HUGE_SECTOR_X(x)           ( WATER_UNSIGN_X(x)					/ HUGE_SECTOR_SIZE      )
+#define WATER_TO_HUGE_SECTOR_Y(y)           ( WATER_UNSIGN_Y(y)					/ HUGE_SECTOR_SIZE      )
+
+// 256	
+#define WATER_EXTRAHUGE_X(x)                ( WATER_UNSIGN_X(x)					/ MAX_EXTRAHUGE_SECTORS )
+#define WATER_EXTRAHUGE_Y(y)                ( WATER_UNSIGN_Y(y)					/ MAX_EXTRAHUGE_SECTORS )
+#define WATER_FROM_EXTRAHUGE_SECTOR_X(x)    ( ((x) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE )
+#define WATER_FROM_EXTRAHUGE_SECTOR_Y(y)    ( ((y) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE )
+#define WATER_TO_EXTRAHUGE_SECTOR_X(x)      ( WATER_UNSIGN_X(x)					/ EXTRAHUGE_SECTOR_SIZE )
+#define WATER_TO_EXTRAHUGE_SECTOR_Y(y)      ( WATER_UNSIGN_Y(y)					/ EXTRAHUGE_SECTOR_SIZE )
+
+
+#define MAX_BOAT_WAKES 8
+
 class CWaterLevel
 {
+	static int32       ms_nNoOfWaterLevels;
+	static float       ms_aWaterZs[48];
+	static CRect       ms_aWaterRects[48];
+	static uint8       aWaterBlockList[WATER_BLOCK_SIZE][WATER_BLOCK_SIZE];
+	static uint8       aWaterFineBlockList[WATER_FINEBLOCK_SIZE][WATER_FINEBLOCK_SIZE];
+	static bool        WavesCalculatedThisFrame;
+	static RpAtomic   *ms_pWavyAtomic;
+	static RpGeometry *apGeomArray[MAX_BOAT_WAKES];
+	static int16       nGeomUsed;
+
 public:
-	static void RenderWater(void);
+	static void    Initialise(char *pWaterDat);
+	static void    Shutdown();
+	static void    CreateWavyAtomic();
+	static void    DestroyWavyAtomic();
+	static bool    GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ);
+	static bool    GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel);
+	static void    RenderWater();
+	static void    RenderOneFlatSmallWaterPoly    (float fX, float fY, float fZ, RwRGBA const &color);
+	static void    RenderOneFlatLargeWaterPoly    (float fX, float fY, float fZ, RwRGBA const &color);	
+	static void    RenderOneFlatHugeWaterPoly     (float fX, float fY, float fZ, RwRGBA const &color);
+	static void    RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color);
+	static void    RenderOneWavySector            (float fX, float fY, float fZ, RwRGBA const &color, bool bUnk = false);
+	static float   CalcDistanceToWater(float fX, float fY);
+	static void    RenderAndEmptyRenderBuffer();	
+	static void    AllocateBoatWakeArray();
+	static void    FreeBoatWakeArray();
 };
diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp
index e46d7247..53a912b3 100644
--- a/src/vehicles/Boat.cpp
+++ b/src/vehicles/Boat.cpp
@@ -2,6 +2,17 @@
 #include "patcher.h"
 #include "Boat.h"
 
+float &fShapeLength = *(float*)0x600E78;
+float &fShapeTime   = *(float*)0x600E7C;
+float &fRangeMult   = *(float*)0x600E80; //0.6f; // 0.75f gta 3
+float &fTimeMult = *(float*)0xA0FCF4;
+
+float MAX_WAKE_LENGTH = 50.0f;
+float MIN_WAKE_INTERVAL = 1.0f;
+float WAKE_LIFETIME = 400.0f;
+
+CBoat * (&CBoat::apFrameWakeGeneratingBoats)[4] = *(CBoat * (*)[4])*(uintptr*)0x8620E0;
+
 CBoat::CBoat(int mi, uint8 owner)
 {
 	ctor(mi, owner);
@@ -9,6 +20,56 @@ CBoat::CBoat(int mi, uint8 owner)
 
 WRAPPER CBoat* CBoat::ctor(int, uint8) { EAXJMP(0x53E3E0); }
 
+bool CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats)
+{
+	uint8 numVerts = 0;
+	
+	if ( apFrameWakeGeneratingBoats[0] == NULL )
+		return false;
+	
+	for ( int32 i = 0; i < 4; i++ )
+	{
+		CBoat *pBoat = apFrameWakeGeneratingBoats[i];
+		if ( !pBoat )
+			break;
+		
+		for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ )
+		{
+			 float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize;
+			 
+			 if ( fabs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist
+				&& fabs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist )
+			 {
+				 apBoats[numVerts] = pBoat;
+				 numVerts = 1; // += ?
+				 break;
+			 }
+		}
+	}
+	
+	return numVerts != 0;
+}
+
+float CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat)
+{
+	for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ )
+	{
+		float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength;
+		
+		float fX = pBoat->m_avec2dWakePoints[i].x - vecVertex.x;
+		float fY = pBoat->m_avec2dWakePoints[i].y - vecVertex.y;
+		
+		float fDist = fY * fY + fX * fX;
+		
+		if ( fDist < SQR(fMaxDist) )
+			return 1.0f - min(fRangeMult * sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f);
+	}
+
+	return 0.0f;
+}
+
+WRAPPER void CBoat::FillBoatList(void) { EAXJMP(0x542250); }
+
 class CBoat_ : public CBoat
 {
 public:
diff --git a/src/vehicles/Boat.h b/src/vehicles/Boat.h
index 6d365395..52f3530c 100644
--- a/src/vehicles/Boat.h
+++ b/src/vehicles/Boat.h
@@ -6,11 +6,53 @@ class CBoat : public CVehicle
 {
 public:
 	// 0x288
-	uint8 stuff1[57];
+	float field_288;
+	float field_28C;
+	float field_290;
+	float field_294;
+	float field_298;
+	float field_29C;
+	float field_2A0;
+	float field_2A4;
+	float m_fMovingHiRotation;
+	int32 _unk0;
+	RwFrame *m_aBoatNodes[4];
+	uint8 m_nBoatFlags;
 	bool m_bIsAnchored;
-	uint8 stuff[450];
+	char _pad0[2];
+	float field_2C4;
+	int32 _unk1;
+	float field_2CC;
+	CEntity *field_2D0;
+	bool _unk2;
+	char _pad1[3];
+	float m_fAccelerate;
+	float m_fBrake;
+	float m_fSteeringLeftRight;
+	uint8 m_nPadID;
+	char _pad2[3];
+	int32 _unk3;
+	float m_fTurnForceZ;
+	CVector m_vecMoveForce;
+	float field_2FC;
+	uint16 field_300;
+	uint16 m_nNumWakePoints;
+	CVector2D m_avec2dWakePoints[32];
+	float m_afWakePointLifeTime[32];
 
 	CBoat(int, uint8);
 	CBoat* ctor(int, uint8);
+	void dtor() { this->CBoat::~CBoat(); };
+	
+	static CBoat *(&apFrameWakeGeneratingBoats)[4];
+	
+	static bool IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats);
+	static float IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat);
+	static void FillBoatList(void);
+
 };
 static_assert(sizeof(CBoat) == 0x484, "CBoat: error");
+
+extern float MAX_WAKE_LENGTH;
+extern float MIN_WAKE_INTERVAL;
+extern float WAKE_LIFETIME;
\ No newline at end of file