diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp
index 539c9e91..930c03a6 100644
--- a/src/audio/AudioManager.cpp
+++ b/src/audio/AudioManager.cpp
@@ -7533,8 +7533,8 @@ cAudioManager::ProcessVehicleSkidding(cVehicleParams *params)
 void cAudioManager::ProcessWaterCannon(int32)
 {
 	for(int32 i = 0; i < NUM_WATERCANNONS; i++) {
-		if(aCannons[i].m_nId) {
-			m_sQueueSample.m_vecPos = aCannons[0].m_avecPos[aCannons[i].m_wIndex];
+		if(CWaterCannons::aCannons[i].m_nId) {
+			m_sQueueSample.m_vecPos = CWaterCannons::aCannons[0].m_avecPos[CWaterCannons::aCannons[i].m_nCur];
 			float distSquared = GetDistanceSquared(&m_sQueueSample.m_vecPos);
 			if(distSquared < 900.f) {
 				m_sQueueSample.m_fDistance = Sqrt(distSquared);
diff --git a/src/math/Vector.h b/src/math/Vector.h
index cd436123..6f544ada 100644
--- a/src/math/Vector.h
+++ b/src/math/Vector.h
@@ -38,6 +38,14 @@ public:
 		}else
 			x = 1.0f;
 	}
+	
+	void Normalise(float norm) {
+		float sq = MagnitudeSqr();
+		float invsqrt = RecipSqrt(norm, sq);
+		x *= invsqrt;
+		y *= invsqrt;
+		z *= invsqrt;
+	}
 
 	const CVector &operator+=(CVector const &right) {
 		x += right.x;
diff --git a/src/render/WaterCannon.cpp b/src/render/WaterCannon.cpp
index 7a9aa4d9..e848fb43 100644
--- a/src/render/WaterCannon.cpp
+++ b/src/render/WaterCannon.cpp
@@ -1,10 +1,320 @@
 #include "common.h"
 #include "patcher.h"
 #include "WaterCannon.h"
+#include "Vector.h"
+#include "General.h"
+#include "main.h"
+#include "Timer.h"
+#include "Pools.h"
+#include "Ped.h"
+#include "AnimManager.h"
+#include "Fire.h"
+#include "WaterLevel.h"
+#include "Camera.h"
 
-CWaterCannon (&aCannons)[NUM_WATERCANNONS] = *(CWaterCannon(*)[NUM_WATERCANNONS])*(uintptr*)0x8F2CA8;
+#define WATERCANNONVERTS 4
+#define WATERCANNONINDEXES 12
 
-WRAPPER void CWaterCannons::Update(void) { EAXJMP(0x522510); }
-WRAPPER void CWaterCannons::UpdateOne(uint32 id, CVector *pos, CVector *dir) { EAXJMP(0x522470); }
-WRAPPER void CWaterCannons::Render(void) { EAXJMP(0x522550); }
-WRAPPER void CWaterCannons::Init(void) { EAXJMP(0x522440); }
+RwIm3DVertex WaterCannonVertices[WATERCANNONVERTS];
+RwImVertexIndex WaterCannonIndexList[WATERCANNONINDEXES];
+
+CWaterCannon CWaterCannons::aCannons[NUM_WATERCANNONS];
+
+void CWaterCannon::Init(void)
+{
+	m_nId = 0;
+	m_nCur = 0;
+	m_nTimeCreated = CTimer::GetTimeInMilliseconds();
+	
+	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
+		m_abUsed[i] = false;
+	
+	RwIm3DVertexSetU(&WaterCannonVertices[0], 0.0f);
+	RwIm3DVertexSetV(&WaterCannonVertices[0], 0.0f);
+	
+	RwIm3DVertexSetU(&WaterCannonVertices[1], 1.0f);
+	RwIm3DVertexSetV(&WaterCannonVertices[1], 0.0f);
+	
+	RwIm3DVertexSetU(&WaterCannonVertices[2], 0.0f);
+	RwIm3DVertexSetV(&WaterCannonVertices[2], 0.0f);
+	
+	RwIm3DVertexSetU(&WaterCannonVertices[3], 1.0f);
+	RwIm3DVertexSetV(&WaterCannonVertices[3], 0.0f);
+	
+	WaterCannonIndexList[0]  = 0;
+	WaterCannonIndexList[1]  = 1;
+	WaterCannonIndexList[2]  = 2;
+	
+	WaterCannonIndexList[3]  = 1;
+	WaterCannonIndexList[4]  = 3;
+	WaterCannonIndexList[5]  = 2;
+	
+	WaterCannonIndexList[6]  = 0;
+	WaterCannonIndexList[7]  = 2;
+	WaterCannonIndexList[8]  = 1;
+	
+	WaterCannonIndexList[9]  = 1;
+	WaterCannonIndexList[10] = 2;
+	WaterCannonIndexList[11] = 3;
+}
+
+void CWaterCannon::Update_OncePerFrame(int16 index)
+{
+	ASSERT(index < NUM_WATERCANNONS);
+	
+	if (CTimer::GetTimeInMilliseconds() > m_nTimeCreated + WATERCANNON_LIFETIME )
+	{
+		m_nCur = (m_nCur + 1) % -NUM_SEGMENTPOINTS;
+		m_abUsed[m_nCur] = false;
+	}
+	
+	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
+	{
+		if ( m_abUsed[i] )
+		{
+			m_avecVelocity[i].z += -WATERCANNON_GRAVITY * CTimer::GetTimeStep();
+			m_avecPos[i]        += m_avecVelocity[i]    * CTimer::GetTimeStep();
+		}
+	}
+	
+	int32 extinguishingPoint = CGeneral::GetRandomNumber() & (NUM_SEGMENTPOINTS - 1);
+	if ( m_abUsed[extinguishingPoint] )
+		gFireManager.ExtinguishPoint(m_avecPos[extinguishingPoint], 3.0f);
+	
+	if ( ((index + CTimer::GetFrameCounter()) & 3) == 0 )
+		PushPeds();
+	
+	// free if unused
+	
+	int32 i = 0;
+	while ( 1 )
+	{
+		if ( m_abUsed[i] )
+			break;
+		
+		if ( ++i >= NUM_SEGMENTPOINTS )
+		{
+			m_nId = 0;
+			return;
+		}
+	}
+}
+
+void CWaterCannon::Update_NewInput(CVector *pos, CVector *dir)
+{
+	ASSERT(pos != NULL);
+	ASSERT(dir != NULL);
+	
+	m_avecPos[m_nCur]      = *pos;
+	m_avecVelocity[m_nCur] = *dir;
+	m_abUsed[m_nCur]       = true;
+}
+
+void CWaterCannon::Render(void)
+{
+	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE,      (void *)FALSE);
+	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
+	RwRenderStateSet(rwRENDERSTATEFOGENABLE,         (void *)TRUE);
+	RwRenderStateSet(rwRENDERSTATETEXTURERASTER,     (void *)gpWaterRaster);
+	
+	float v = float(CGeneral::GetRandomNumber() & 255) / 256;
+		
+	RwIm3DVertexSetV(&WaterCannonVertices[0], v);
+	RwIm3DVertexSetV(&WaterCannonVertices[1], v);
+	RwIm3DVertexSetV(&WaterCannonVertices[2], v);
+	RwIm3DVertexSetV(&WaterCannonVertices[3], v);
+	
+	int16 pointA = m_nCur % -NUM_SEGMENTPOINTS;
+	
+	int16 pointB = pointA - 1;
+	if ( (pointA - 1) < 0 )
+		pointB += NUM_SEGMENTPOINTS;
+
+	bool bInit = false;
+	CVector norm;
+	
+	for ( int32 i = 0; i < NUM_SEGMENTPOINTS - 1; i++ )
+	{
+		if ( m_abUsed[pointA] && m_abUsed[pointB] )
+		{
+			if ( !bInit )
+			{
+				CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward());
+				cp.Normalise(0.05f);
+				norm = cp;
+				bInit = true;
+			}
+			
+			float dist       = float(i*i*i) / 300.0f + 1.0f;
+			float brightness = float(i) / NUM_SEGMENTPOINTS;
+			
+			int32 color = (int32)((1.0f - brightness*brightness) * 255.0f);
+			CVector offset = dist * norm;
+			
+			RwIm3DVertexSetRGBA(&WaterCannonVertices[0], color, color, color, color);
+			RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z);
+				
+			RwIm3DVertexSetRGBA(&WaterCannonVertices[1], color, color, color, color);
+			RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z);
+			
+			RwIm3DVertexSetRGBA(&WaterCannonVertices[2], color, color, color, color);
+			RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z);
+			
+			RwIm3DVertexSetRGBA(&WaterCannonVertices[3], color, color, color, color);
+			RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z);
+			
+			LittleTest();
+
+			if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) )
+			{
+				RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES);
+				RwIm3DEnd();
+			}
+		}
+		
+		pointA = pointB--;
+		if ( pointB < 0 )
+			pointB += NUM_SEGMENTPOINTS;
+	}
+	
+	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE,      (void *)TRUE);
+	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
+	RwRenderStateSet(rwRENDERSTATEFOGENABLE,         (void *)FALSE);
+}
+
+void CWaterCannon::PushPeds(void)
+{
+	float minx = 10000.0f;
+	float maxx = -10000.0f;
+	float miny = 10000.0f;
+	float maxy = -10000.0f;
+	float minz = 10000.0f;
+	float maxz = -10000.0f;
+  
+	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
+	{
+		if ( m_abUsed[i] )
+		{
+			minx = min(minx, m_avecPos[i].x);
+			maxx = max(maxx, m_avecPos[i].x);
+			
+			miny = min(miny, m_avecPos[i].y);
+			maxy = max(maxy, m_avecPos[i].y);
+			
+			minz = min(minz, m_avecPos[i].z);
+			maxz = max(maxz, m_avecPos[i].z);
+		}
+	}
+	
+	for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--)
+	{
+		CPed *ped = CPools::GetPedPool()->GetSlot(i);
+		if ( ped )
+		{
+			if (   ped->GetPosition().x > minx && ped->GetPosition().x < maxx
+				&& ped->GetPosition().y > miny && ped->GetPosition().y < maxy
+				&& ped->GetPosition().z > minz && ped->GetPosition().z < maxz )
+			{
+				for ( int32 j = 0; j < NUM_SEGMENTPOINTS; j++ )
+				{
+					if ( m_abUsed[j] )
+					{
+						CVector dist = m_avecPos[j] - ped->GetPosition();
+						
+						if ( dist.MagnitudeSqr() < 5.0f )
+						{
+							int32 localDir = ped->GetLocalDirection(CVector2D(1.0f, 0.0f));
+							
+							ped->bIsStanding = false;
+							
+							ped->ApplyMoveForce(0.0f, 0.0f, 2.0f * CTimer::GetTimeStep());
+							
+							ped->m_vecMoveSpeed.x = (0.6f * m_avecVelocity[j].x + ped->m_vecMoveSpeed.x) * 0.5f;
+							ped->m_vecMoveSpeed.y = (0.6f * m_avecVelocity[j].y + ped->m_vecMoveSpeed.y) * 0.5f;
+							
+							ped->SetFall(2000, AnimationId(ANIM_KO_SKID_FRONT + localDir), 0);
+							
+							CFire *fire = ped->m_pFire;
+							if ( fire )
+								fire->Extinguish();
+							
+							j = NUM_SEGMENTPOINTS;
+						}
+					}
+				}
+			}
+		}
+	}	
+}
+
+void CWaterCannons::Init(void)
+{
+	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
+		aCannons[i].Init();
+}
+
+void CWaterCannons::UpdateOne(uint32 id, CVector *pos, CVector *dir)
+{
+	ASSERT(pos != NULL);
+	ASSERT(dir != NULL);
+	
+	// find the one by id
+	{
+		int32 n = 0;
+		while ( n < NUM_WATERCANNONS && id != aCannons[n].m_nId )
+			n++;
+		
+		if ( n < NUM_WATERCANNONS )
+		{
+			aCannons[n].Update_NewInput(pos, dir);
+			return;
+		}
+	}
+	
+	// if no luck then find a free one
+	{
+		int32 n = 0;
+		while ( n < NUM_WATERCANNONS && 0 != aCannons[n].m_nId )
+			n++;
+		
+		if ( n < NUM_WATERCANNONS )
+		{
+			aCannons[n].Init();
+			aCannons[n].m_nId = id;
+			aCannons[n].Update_NewInput(pos, dir);
+			return;
+		}
+	}
+}
+
+void CWaterCannons::Update(void)
+{
+	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
+	{
+		if ( aCannons[i].m_nId != 0 )
+			aCannons[i].Update_OncePerFrame(i);
+	}
+}
+
+void CWaterCannons::Render(void)
+{
+	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
+	{
+		if ( aCannons[i].m_nId != 0 )
+			aCannons[i].Render();
+	}
+}
+
+STARTPATCHES
+	InjectHook(0x521A30, &CWaterCannon::Init, PATCH_JUMP);
+	InjectHook(0x521B80, &CWaterCannon::Update_OncePerFrame, PATCH_JUMP);
+	InjectHook(0x521CC0, &CWaterCannon::Update_NewInput, PATCH_JUMP);
+	InjectHook(0x521D30, &CWaterCannon::Render, PATCH_JUMP);
+	InjectHook(0x5220B0, &CWaterCannon::PushPeds, PATCH_JUMP);
+	InjectHook(0x522440, CWaterCannons::Init, PATCH_JUMP);
+	InjectHook(0x522470, CWaterCannons::UpdateOne, PATCH_JUMP);
+	InjectHook(0x522510, CWaterCannons::Update, PATCH_JUMP);
+	InjectHook(0x522550, CWaterCannons::Render, PATCH_JUMP);
+	//InjectHook(0x522B40, `global constructor keyed to'watercannon.cpp, PATCH_JUMP);
+	//InjectHook(0x522B60, CWaterCannon::CWaterCannon, PATCH_JUMP);
+ENDPATCHES
\ No newline at end of file
diff --git a/src/render/WaterCannon.h b/src/render/WaterCannon.h
index c2b288f2..826dc78e 100644
--- a/src/render/WaterCannon.h
+++ b/src/render/WaterCannon.h
@@ -1,15 +1,29 @@
 #pragma once
 
+#define WATERCANNON_GRAVITY (0.009f)
+#define WATERCANNON_LIFETIME (150)
+
 class CWaterCannon
 {
 public:
+	enum
+	{
+		NUM_SEGMENTPOINTS = 16,
+	};
+	
 	int32 m_nId;
-	int16 m_wIndex;
-	char gap_6[2];
-	int32 m_nTimeCreated;
-	CVector m_avecPos[16];
-	CVector m_avecVelocity[16];
-	char m_abUsed[16];
+	int16 m_nCur;
+	char _pad0[2];
+	uint32 m_nTimeCreated;
+	CVector m_avecPos[NUM_SEGMENTPOINTS];
+	CVector m_avecVelocity[NUM_SEGMENTPOINTS];
+	bool m_abUsed[NUM_SEGMENTPOINTS];
+	
+	void Init(void);
+	void Update_OncePerFrame(int16 index);
+	void Update_NewInput(CVector *pos, CVector *dir);
+	void Render(void);
+	void PushPeds(void);
 };
 
 static_assert(sizeof(CWaterCannon) == 412, "CWaterCannon: error");
@@ -17,11 +31,10 @@ static_assert(sizeof(CWaterCannon) == 412, "CWaterCannon: error");
 class CWaterCannons
 {
 public:
-	static void Update();
-	static void UpdateOne(uint32 id, CVector *pos, CVector *dir);
-	static void Render(void);
+	static CWaterCannon aCannons[NUM_WATERCANNONS];
+	
 	static void Init(void);
-};
-
-extern CWaterCannon (&aCannons)[NUM_WATERCANNONS];
-
+	static void UpdateOne(uint32 id, CVector *pos, CVector *dir);
+	static void Update();
+	static void Render(void);
+};
\ No newline at end of file
diff --git a/src/skel/win/resource.h b/src/skel/win/resource.h
index 2fb3dc50..84dffb95 100644
--- a/src/skel/win/resource.h
+++ b/src/skel/win/resource.h
@@ -8,6 +8,7 @@
 #define IDEXIT                          1002
 #define IDC_SELECTDEVICE                1005
 
+#define IDI_MAIN_ICON                   1042
 // Next default values for new objects
 // 
 #ifdef APSTUDIO_INVOKED
diff --git a/src/skel/win/win.rc b/src/skel/win/win.rc
index 676b8ef7..379c473d 100644
--- a/src/skel/win/win.rc
+++ b/src/skel/win/win.rc
@@ -30,8 +30,18 @@ BEGIN
                     WS_TABSTOP
     DEFPUSHBUTTON   "EXIT",IDEXIT,103,69,52,14
     DEFPUSHBUTTON   "OK",IDOK,28,69,50,14
-    LTEXT           "Please select the device to use:",IDC_SELECTDEVICE,7,7,
+    LTEXT           "Please select the Device To Use:",IDC_SELECTDEVICE,7,7,
                     137,8
 END
 
 
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_MAIN_ICON           ICON    DISCARDABLE     "gta3.ico"
+
+/////////////////////////////////////////////////////////////////////////////
\ No newline at end of file