code:
#pragma once

class CTimeCycle
{
	static int32 m_nAmbientRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nAmbientGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nAmbientBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nDirectionalRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nDirectionalGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nDirectionalBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyTopRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyTopGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyTopBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyBottomRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoreRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoreGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoreBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoronaRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS];
	static float m_fSunSize[NUMHOURS][NUMWEATHERS];
	static float m_fSpriteSize[NUMHOURS][NUMWEATHERS];
	static float m_fSpriteBrightness[NUMHOURS][NUMWEATHERS];
	static int16 m_nShadowStrength[NUMHOURS][NUMWEATHERS];
	static int16 m_nLightShadowStrength[NUMHOURS][NUMWEATHERS];
	static int16 m_nTreeShadowStrength[NUMHOURS][NUMWEATHERS];
	static float m_fFogStart[NUMHOURS][NUMWEATHERS];
	static float m_fFarClip[NUMHOURS][NUMWEATHERS];
	static float m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS];
	static int32 m_nLowCloudsRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS];
	static int32 m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS];
	static float m_fBlurRed[NUMHOURS][NUMWEATHERS];
	static float m_fBlurGreen[NUMHOURS][NUMWEATHERS];
	static float m_fBlurBlue[NUMHOURS][NUMWEATHERS];
	static float m_fBlurAlpha[NUMHOURS][NUMWEATHERS];

	static float m_fCurrentAmbientRed;
	static float m_fCurrentAmbientGreen;
	static float m_fCurrentAmbientBlue;
	static float m_fCurrentDirectionalRed;
	static float m_fCurrentDirectionalGreen;
	static float m_fCurrentDirectionalBlue;
	static int32 m_nCurrentSkyTopRed;
	static int32 m_nCurrentSkyTopGreen;
	static int32 m_nCurrentSkyTopBlue;
	static int32 m_nCurrentSkyBottomRed;
	static int32 m_nCurrentSkyBottomGreen;
	static int32 m_nCurrentSkyBottomBlue;
	static int32 m_nCurrentSunCoreRed;
	static int32 m_nCurrentSunCoreGreen;
	static int32 m_nCurrentSunCoreBlue;
	static int32 m_nCurrentSunCoronaRed;
	static int32 m_nCurrentSunCoronaGreen;
	static int32 m_nCurrentSunCoronaBlue;
	static float m_fCurrentSunSize;
	static float m_fCurrentSpriteSize;
	static float m_fCurrentSpriteBrightness;
	static int32 m_nCurrentShadowStrength;
	static int32 m_nCurrentLightShadowStrength;
	static int32 m_nCurrentTreeShadowStrength;
	static float m_fCurrentFogStart;
	static float m_fCurrentFarClip;
	static float m_fCurrentLightsOnGroundBrightness;
	static int32 m_nCurrentLowCloudsRed;
	static int32 m_nCurrentLowCloudsGreen;
	static int32 m_nCurrentLowCloudsBlue;
	static int32 m_nCurrentFluffyCloudsTopRed;
	static int32 m_nCurrentFluffyCloudsTopGreen;
	static int32 m_nCurrentFluffyCloudsTopBlue;
	static int32 m_nCurrentFluffyCloudsBottomRed;
	static int32 m_nCurrentFluffyCloudsBottomGreen;
	static int32 m_nCurrentFluffyCloudsBottomBlue;
	static float m_fCurrentBlurRed;
	static float m_fCurrentBlurGreen;
	static float m_fCurrentBlurBlue;
	static float m_fCurrentBlurAlpha;
	static int32 m_nCurrentFogColourRed;
	static int32 m_nCurrentFogColourGreen;
	static int32 m_nCurrentFogColourBlue;

	static int32 m_FogReduction;

public:
	static int32 m_CurrentStoredValue;
	static CVector m_VectorToSun[16];
	static float m_fShadowFrontX[16];
	static float m_fShadowFrontY[16];
	static float m_fShadowSideX[16];
	static float m_fShadowSideY[16];
	static float m_fShadowDisplacementX[16];
	static float m_fShadowDisplacementY[16];

	static float GetAmbientRed(void) { return m_fCurrentAmbientRed; }
	static float GetAmbientGreen(void) { return m_fCurrentAmbientGreen; }
	static float GetAmbientBlue(void) { return m_fCurrentAmbientBlue; }
	static float GetDirectionalRed(void) { return m_fCurrentDirectionalRed; }
	static float GetDirectionalGreen(void) { return m_fCurrentDirectionalGreen; }
	static float GetDirectionalBlue(void) { return m_fCurrentDirectionalBlue; }
	static int32 GetSkyTopRed(void) { return m_nCurrentSkyTopRed; }
	static int32 GetSkyTopGreen(void) { return m_nCurrentSkyTopGreen; }
	static int32 GetSkyTopBlue(void) { return m_nCurrentSkyTopBlue; }
	static int32 GetSkyBottomRed(void) { return m_nCurrentSkyBottomRed; }
	static int32 GetSkyBottomGreen(void) { return m_nCurrentSkyBottomGreen; }
	static int32 GetSkyBottomBlue(void) { return m_nCurrentSkyBottomBlue; }
	static int32 GetSunCoreRed(void) { return m_nCurrentSunCoreRed; }
	static int32 GetSunCoreGreen(void) { return m_nCurrentSunCoreGreen; }
	static int32 GetSunCoreBlue(void) { return m_nCurrentSunCoreBlue; }
	static int32 GetSunCoronaRed(void) { return m_nCurrentSunCoronaRed; }
	static int32 GetSunCoronaGreen(void) { return m_nCurrentSunCoronaGreen; }
	static int32 GetSunCoronaBlue(void) { return m_nCurrentSunCoronaBlue; }
	static float GetSunSize(void) { return m_fCurrentSunSize; }
	static float GetSpriteBrightness(void) { return m_fCurrentSpriteBrightness; }
	static float GetSpriteSize(void) { return m_fCurrentSpriteSize; }
	static int32 GetShadowStrength(void) { return m_nCurrentShadowStrength; }
	static int32 GetLightShadowStrength(void) { return m_nCurrentLightShadowStrength; }
	static float GetLightOnGroundBrightness(void) { return m_fCurrentLightsOnGroundBrightness; }
	static float GetFarClip(void) { return m_fCurrentFarClip; }
	static float GetFogStart(void) { return m_fCurrentFogStart; }

	static int32 GetLowCloudsRed(void) { return m_nCurrentLowCloudsRed; }
	static int32 GetLowCloudsGreen(void) { return m_nCurrentLowCloudsGreen; }
	static int32 GetLowCloudsBlue(void) { return m_nCurrentLowCloudsBlue; }
	static int32 GetFluffyCloudsTopRed(void) { return m_nCurrentFluffyCloudsTopRed; }
	static int32 GetFluffyCloudsTopGreen(void) { return m_nCurrentFluffyCloudsTopGreen; }
	static int32 GetFluffyCloudsTopBlue(void) { return m_nCurrentFluffyCloudsTopBlue; }
	static int32 GetFluffyCloudsBottomRed(void) { return m_nCurrentFluffyCloudsBottomRed; }
	static int32 GetFluffyCloudsBottomGreen(void) { return m_nCurrentFluffyCloudsBottomGreen; }
	static int32 GetFluffyCloudsBottomBlue(void) { return m_nCurrentFluffyCloudsBottomBlue; }
	static int32 GetFogRed(void) { return m_nCurrentFogColourRed; }
	static int32 GetFogGreen(void) { return m_nCurrentFogColourGreen; }
	static int32 GetFogBlue(void) { return m_nCurrentFogColourBlue; }
	static int32 GetFogReduction(void) { return m_FogReduction; }

	static void Initialise(void);
	static void Update(void);
	static CVector &GetSunDirection(void) { return m_VectorToSun[m_CurrentStoredValue]; }
	static float GetShadowFrontX(void) { return m_fShadowFrontX[m_CurrentStoredValue]; }
	static float GetShadowFrontY(void) { return m_fShadowFrontY[m_CurrentStoredValue]; }
	static float GetShadowSideX(void) { return m_fShadowSideX[m_CurrentStoredValue]; }
	static float GetShadowSideY(void) { return m_fShadowSideY[m_CurrentStoredValue]; }
	static float GetShadowDisplacementX(void) { return m_fShadowDisplacementX[m_CurrentStoredValue]; }
	static float GetShadowDisplacementY(void) { return m_fShadowDisplacementY[m_CurrentStoredValue]; }
};


code:
#include "common.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"

#define WATERCANNONVERTS 4
#define WATERCANNONINDEXES 12

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 ( pointB < 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());
				norm = cp * (0.05f / cp.Magnitude());
				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_STD_HIGHIMPACT_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)
{
	PUSH_RENDERGROUP("CWaterCannons::Render");
	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
	{
		if ( aCannons[i].m_nId != 0 )
			aCannons[i].Render();
	}
	POP_RENDERGROUP();
}

license: mit


code:
#pragma once

#define WATERCANNON_GRAVITY (0.009f)
#define WATERCANNON_LIFETIME (150)

class CWaterCannon
{
public:
	enum
	{
		NUM_SEGMENTPOINTS = 16,
	};

	int32 m_nId;
	int16 m_nCur;
	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);
};

VALIDATE_SIZE(CWaterCannon, 412);

class CWaterCannons
{
public:
	static CWaterCannon aCannons[NUM_WATERCANNONS];

	static void Init(void);
	static void UpdateOne(uint32 id, CVector *pos, CVector *dir);
	static void Update();
	static void Render(void);
};


code:
#include "common.h"
#include "main.h"
#include "FileMgr.h"
#include "FileLoader.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 "MemoryHeap.h"

float TEXTURE_ADDU;
float TEXTURE_ADDV;

int32 CWaterLevel::ms_nNoOfWaterLevels;
float CWaterLevel::ms_aWaterZs[48];
CRect CWaterLevel::ms_aWaterRects[48];
int8 CWaterLevel::aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS];
int8 CWaterLevel::aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS];
bool CWaterLevel::WavesCalculatedThisFrame;
RpAtomic *CWaterLevel::ms_pWavyAtomic;
RpGeometry *CWaterLevel::apGeomArray[8];
int16 CWaterLevel::nGeomUsed;
//"Custom" Don't Render Water Toggle
bool gbDontRenderWater;

//RwTexture *gpWaterTex;
//RwRaster *gpWaterRaster;

RwTexture *gpWaterTex;
RwRaster *gpWaterRaster;

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(Const char *pWaterDat)
{
	ms_nNoOfWaterLevels = 0;

#ifdef MASTER
	int32 hFile = -1;

	do
	{
		hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb");
	}
	while ( hFile < 0 );
#else
	int32 hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb");
#endif

	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);
	}
#ifndef MASTER
	else
	{
		printf("Init waterlevels\n");

		CFileMgr::SetDir("");
		hFile = CFileMgr::OpenFile(pWaterDat, "r");

		char *line;

		while ((line = CFileLoader::LoadLine(hFile)))
		{
#ifdef FIX_BUGS
			if (*line && *line != ';' && !strstr(line, "* ;end of file"))
#else
			if (*line && *line != ';')
#endif
			{
				float z, l, b, r, t;
				sscanf(line, "%f %f %f %f %f", &z, &l, &b, &r, &t);
				AddWaterLevel(l, b, r, t, z);
			}
		}

		CFileMgr::CloseFile(hFile);

		for (int32 x = 0; x < MAX_SMALL_SECTORS; x++)
		{
			for (int32 y = 0; y < MAX_SMALL_SECTORS; y++)
			{
				aWaterFineBlockList[x][y] = NO_WATER;
			}
		}

		// rasterize water rects read from file
		for (int32 i = 0; i < ms_nNoOfWaterLevels; i++)
		{
			int32 l = WATER_HUGE_X(ms_aWaterRects[i].left);
			int32 r = WATER_HUGE_X(ms_aWaterRects[i].right) + 1.0f;
			int32 t = WATER_HUGE_Y(ms_aWaterRects[i].top);
			int32 b = WATER_HUGE_Y(ms_aWaterRects[i].bottom) + 1.0f;

#ifdef FIX_BUGS
			// water.dat has rects that go out of bounds
			// which causes memory corruption
			l = Clamp(l, 0, MAX_SMALL_SECTORS - 1);
			r = Clamp(r, 0, MAX_SMALL_SECTORS - 1);
			t = Clamp(t, 0, MAX_SMALL_SECTORS - 1);
			b = Clamp(b, 0, MAX_SMALL_SECTORS - 1);
#endif

			for (int32 x = l; x <= r; x++)
			{
				for (int32 y = t; y <= b; y++)
				{
					aWaterFineBlockList[x][y] = i;
				}
			}
		}

		// remove tiles that are obscured by land
		for (int32 x = 0; x < MAX_SMALL_SECTORS; x++)
		{
			float worldX = WATER_START_X + x * SMALL_SECTOR_SIZE;

			for (int32 y = 0; y < MAX_SMALL_SECTORS; y++)
			{
				if (aWaterFineBlockList[x][y] >= 0)
				{
					float worldY = WATER_START_Y + y * SMALL_SECTOR_SIZE;

					int32 i;
					for (i = 0; i <= 8; i++)
					{
						for (int32 j = 0; j <= 8; j++)
						{
							CVector worldPos = CVector(worldX + i * (SMALL_SECTOR_SIZE / 8), worldY + j * (SMALL_SECTOR_SIZE / 8), ms_aWaterZs[aWaterFineBlockList[x][y]]);

							if ((worldPos.x > WORLD_MIN_X && worldPos.x < WORLD_MAX_X) && (worldPos.y > WORLD_MIN_Y && worldPos.y < WORLD_MAX_Y) &&
							    (!WaterLevelAccordingToRectangles(worldPos.x, worldPos.y) || TestVisibilityForFineWaterBlocks(worldPos)))
								continue;

							// at least one point in the tile wasn't blocked, so don't remove water
							i = 1000;
							break;
						}
					}

					if (i < 1000)
						aWaterFineBlockList[x][y] = NO_WATER;
				}
			}
		}

		RemoveIsolatedWater();

		// calculate coarse tiles from fine tiles
		for (int32 x = 0; x < MAX_LARGE_SECTORS; x++)
		{
			for (int32 y = 0; y < MAX_LARGE_SECTORS; y++)
			{
				if (aWaterFineBlockList[x * 2][y * 2] >= 0)
				{
					aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2];
				}
				else if (aWaterFineBlockList[x * 2 + 1][y * 2] >= 0)
				{
					aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2];
				}
				else if (aWaterFineBlockList[x * 2][y * 2 + 1] >= 0)
				{
					aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2 + 1];
				}
				else if (aWaterFineBlockList[x * 2 + 1][y * 2 + 1] >= 0)
				{
					aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2 + 1];
				}
				else
				{
					aWaterBlockList[x][y] = NO_WATER;
				}
			}
		}

		hFile = CFileMgr::OpenFileForWriting("data\\waterpro.dat");

		if (hFile > 0)
		{
			CFileMgr::Write(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels));
			CFileMgr::Write(hFile, (char *)ms_aWaterZs,	sizeof(ms_aWaterZs));
			CFileMgr::Write(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects));
			CFileMgr::Write(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList));
			CFileMgr::Write(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList));

			CFileMgr::CloseFile(hFile);
		}
	}
#endif

	CTxdStore::PushCurrentTxd();

	int32 slot = CTxdStore::FindTxdSlot("particle");
	CTxdStore::SetCurrentTxd(slot);

	if ( gpWaterTex == nil )
		gpWaterTex = RwTextureRead("water_old", nil);
	gpWaterRaster = RwTextureGetRaster(gpWaterTex);

	CTxdStore::PopCurrentTxd();

	CreateWavyAtomic();
	FreeBoatWakeArray();

	printf("Done Initing waterlevels\n");
}

void
CWaterLevel::Shutdown()
{
	FreeBoatWakeArray();
	DestroyWavyAtomic();

	if ( gpWaterTex != nil )
	{
		RwTextureDestroy(gpWaterTex);
		gpWaterTex = nil;
	}
}

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 != nil);

	}

	{
		wavyMaterial = RpMaterialCreate();

		ASSERT(wavyMaterial != nil);
		ASSERT(gpWaterTex   != nil);

		RpMaterialSetTexture(wavyMaterial, gpWaterTex);
	}

	{
		wavyTriangles = RpGeometryGetTriangles(wavyGeometry);

		ASSERT(wavyTriangles != nil);
		/*
			[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);
		ASSERT(wavyMorphTarget != nil);
		wavyVert = RpMorphTargetGetVertices(wavyMorphTarget);
		ASSERT(wavyVert != nil);

		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 != nil );

		ms_pWavyAtomic = RpAtomicCreate();
		ASSERT( ms_pWavyAtomic != nil );

		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);
}

#ifndef MASTER
void
CWaterLevel::AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel)
{
	ms_aWaterRects[ms_nNoOfWaterLevels] = CRect(fXLeft, fYBottom, fXRight, fYTop);
	ms_aWaterZs[ms_nNoOfWaterLevels] = fLevel;
	ms_nNoOfWaterLevels++;
}

bool
CWaterLevel::WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel)
{
	if (ms_nNoOfWaterLevels <= 0) return false;

	for (int32 i = 0; i < ms_nNoOfWaterLevels; i++)
	{
		if (fX >= ms_aWaterRects[i].left && fX <= ms_aWaterRects[i].right
			&& fY >= ms_aWaterRects[i].top && fY <= ms_aWaterRects[i].bottom)
		{
			if (pfOutLevel) *pfOutLevel = ms_aWaterZs[i];

			return true;
		}
	}

	return false;
}

bool
CWaterLevel::TestVisibilityForFineWaterBlocks(const CVector &worldPos)
{
	static CVector2D tab[] =
	{
		{ 50.0f, 50.0f },
		{ -50.0f, 50.0f },
		{ -50.0f, -50.0f },
		{ 50.0f, -50.0f },
		{ 50.0f, 0.0f },
		{ -50.0f, 0.0f },
		{ 0.0f, -50.0f },
		{ 0.0f, 50.0f },
	};

	CEntity *entity;
	CColPoint col;
	CVector lineStart, lineEnd;

	lineStart = worldPos;

	if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil))
	{
		lineStart.x += 0.4f;
		lineStart.y += 0.4f;

		if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil))
		{
			return false;
		}
	}

	for (int32 i = 0; i < ARRAY_SIZE(tab); i++)
	{
		lineStart = worldPos;
		lineEnd = worldPos;

		lineEnd.x += tab[i].x;
		lineEnd.y += tab[i].y;
		lineEnd.z += 100.0f;

		if ((lineEnd.x > WORLD_MIN_X && lineEnd.x < WORLD_MAX_X) && (lineEnd.y > WORLD_MIN_Y && lineEnd.y < WORLD_MAX_Y))
		{
			if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false))
			{
				lineStart.x += 0.4f;
				lineStart.y += 0.4f;
				lineEnd.x += 0.4f;
				lineEnd.y += 0.4f;

				if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false))
				{
					return false;
				}
			}
		}
	}

	return true;
}

void
CWaterLevel::RemoveIsolatedWater()
{
	bool (*isConnected)[MAX_SMALL_SECTORS] = new bool[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS];

	for (int32 x = 0; x < MAX_SMALL_SECTORS; x++)
	{
		for (int32 y = 0; y < MAX_SMALL_SECTORS; y++)
		{
			isConnected[x][y] = false;
		}
	}

	isConnected[0][0] = true;
	bool keepGoing;

	do
	{
		keepGoing = false;

		for (int32 x = 0; x < MAX_SMALL_SECTORS; x++)
		{
			for (int32 y = 0; y < MAX_SMALL_SECTORS; y++)
			{
				if (aWaterFineBlockList[x][y] < 0 || isConnected[x][y])
					continue;

				if (x > 0 && isConnected[x - 1][y])
				{
					isConnected[x][y] = true;
					keepGoing = true;
				}

				if (y > 0 && isConnected[x][y - 1])
				{
					isConnected[x][y] = true;
					keepGoing = true;
				}

				if (x + 1 < MAX_SMALL_SECTORS && isConnected[x + 1][y])
				{
					isConnected[x][y] = true;
					keepGoing = true;
				}

				if (y + 1 < MAX_SMALL_SECTORS && isConnected[x][y + 1])
				{
					isConnected[x][y] = true;
					keepGoing = true;
				}
			}
		}
	}
	while (keepGoing);

	int32 numRemoved = 0;

	for (int32 x = 0; x < MAX_SMALL_SECTORS; x++)
	{
		for (int32 y = 0; y < MAX_SMALL_SECTORS; y++)
		{
			if (aWaterFineBlockList[x][y] >= 0 && !isConnected[x][y] && ms_aWaterZs[aWaterFineBlockList[x][y]] == 0.0f)
			{
				numRemoved++;
				aWaterFineBlockList[x][y] = NO_WATER;
			}
		}
	}

	printf("Removed %d isolated patches of water\n", numRemoved);

	delete[] isConnected;
}
#endif

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 );

	int8 nBlock = aWaterFineBlockList[x][y];

	if ( nBlock == NO_WATER )
		return false;

	ASSERT( pfOutLevel != nil );
	*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 );

	int8 nBlock = aWaterFineBlockList[x][y];

	if ( nBlock == NO_WATER )
		return false;

	ASSERT( pfOutLevel != nil );
	*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 ( Abs(TheCamera.GetForward().x) > Abs(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;
		}
	}
}

inline float
SectorRadius(float fSize)
{
	return Sqrt(Pow(fSize, 2) + Pow(fSize, 2));
}

void
CWaterLevel::RenderWater()
{
//"Custom" Don't Render Water Toggle
#ifndef MASTER
	if (gbDontRenderWater)
		return;
#endif
	PUSH_RENDERGROUP("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() )
	{
#ifdef FIX_BUGS
		TEXTURE_ADDU += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix();
		TEXTURE_ADDV += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix();
#else
		TEXTURE_ADDU += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV;
		TEXTURE_ADDV += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV;
#endif
	}

	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] >= 0
				|| aWaterBlockList[2*x+1][2*y+0] >= 0
				|| aWaterBlockList[2*x+0][2*y+1] >= 0
				|| aWaterBlockList[2*x+1][2*y+1] >= 0 )
			{
				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)) )
					{
						if ( fHugeSectorDistToCamSqr >= SQR(500.0f) /*fHugeSectorNearDist*/ )
						{
							float fZ;

							if ( aWaterBlockList[2*x+0][2*y+0] >= 0 )
								fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+0] ];

							if ( aWaterBlockList[2*x+1][2*y+0] >= 0 )
								fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+0] ];

							if ( aWaterBlockList[2*x+0][2*y+1] >= 0 )
								fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+1] ];

							if ( aWaterBlockList[2*x+1][2*y+1] >= 0 )
								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] >= 0 )
									{
										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,
											{
												// 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] >= 0 )
													{
														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] >= 0 )
													{
														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] >= 0 )
													{
														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] >= 0 )
													{
														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] >= 0 )
								}	//	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)) )
					{
						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)) )
					{
						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)) )
					{
						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 ( Abs(prev_pos.x - cur_pos.x) + Abs(prev_pos.y - cur_pos.y) + Abs(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 nRot = CGeneral::GetRandomNumber() % CParticle::SIN_COS_TABLE_SIZE-1;

				float fCos = CParticle::Cos(nRot);
				float fSin = CParticle::Sin(nRot);

				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();

	POP_RENDERGROUP();
}

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 != nil );

		RpGeometry *geometry = RpAtomicGetGeometry(ms_pWavyAtomic);

		ASSERT( geometry != nil );

		RwRGBA      *wavyPreLights = RpGeometryGetPreLightColors(geometry);
		RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(geometry, rwTEXTURECOORDINATEINDEX0);
		RwV3d       *wavyVertices  = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geometry, 0));

		ASSERT( wavyPreLights != nil );
		ASSERT( wavyTexCoords != nil );
		ASSERT( wavyVertices  != nil );

		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 )
										* ( Sin(float(i + j) * DEGTORAD(45.0f) + fAngle) )
										+ ( CWeather::Wind * 0.2f * Sin(float(j - i) * PI + (2.0f * fAngle)) );
			}
		}

		RpGeometryUnlock(geometry);
	}

	static CBoat *apBoatList[4] = { nil };

	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 != nil );
		ASSERT( geom != nil );

		RpAtomic *atomic = RpAtomicCreate();
		ASSERT( atomic != nil );

		RpAtomicSetGeometry(atomic, geom, 0);

		RwFrame *frame = RwFrameCreate();
		ASSERT( frame != nil );

		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 != nil );
		ASSERT( wavyTexCoord  != nil );
		ASSERT( geomPreLights != nil );
		ASSERT( geomVertices  != nil );
		ASSERT( wavyVertices  != nil );

		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] != nil )
						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 != nil );

		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] >= 0 )
			{
				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, nil, rwIM3D_VERTEXUV) )
		{
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
			RwIm3DEnd();
		}
	}

	TempBufferIndicesStored = 0;
	TempBufferVerticesStored = 0;
}

void
CWaterLevel::AllocateBoatWakeArray()
{
	CStreaming::MakeSpaceFor(14 * CDSTREAM_SECTOR_SIZE);

	PUSH_MEMID(MEMID_STREAM);

	ASSERT(ms_pWavyAtomic != nil );

	RpGeometry    *wavyGeometry    = RpAtomicGetGeometry(ms_pWavyAtomic);
	ASSERT(wavyGeometry    != nil );
	RpMorphTarget *wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0);
	RpMaterial    *wavyMaterial    = RpGeometryGetMaterial(wavyGeometry, 0);

	ASSERT(wavyMorphTarget != nil );
	ASSERT(wavyMaterial    != nil );

	for ( int32 geom = 0; geom < MAX_BOAT_WAKES; geom++ )
	{
		if ( apGeomArray[geom] == nil )
		{
			apGeomArray[geom] = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP
															| rpGEOMETRYPRELIT
															| rpGEOMETRYMODULATEMATERIALCOLOR
															| rpGEOMETRYTEXTURED);
			ASSERT(apGeomArray[geom] != nil);

			RpTriangle *geomTriangles = RpGeometryGetTriangles(apGeomArray[geom]);

			ASSERT( geomTriangles != nil );

			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 != nil );
			ASSERT( geomVertices != nil );

			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]);
		}
	}

	POP_MEMID();
}

void
CWaterLevel::FreeBoatWakeArray()
{
	for ( int32 i = 0; i < MAX_BOAT_WAKES; i++ )
	{
		if ( apGeomArray[i] != nil )
		{
			RpGeometryDestroy(apGeomArray[i]);
			apGeomArray[i] = nil;
		}
	}

	nGeomUsed = 0;
}


code:
#pragma once

#define WATER_Z_OFFSET (1.5f)

#define NO_WATER -128

#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

extern RwRaster* gpWaterRaster;
extern bool gbDontRenderWater;

class CWaterLevel
{
	static int32       ms_nNoOfWaterLevels;
	static float       ms_aWaterZs[48];
	static CRect       ms_aWaterRects[48];
	static int8        aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS];
	static int8        aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS];
	static bool        WavesCalculatedThisFrame;
	static RpAtomic   *ms_pWavyAtomic;
	static RpGeometry *apGeomArray[MAX_BOAT_WAKES];
	static int16       nGeomUsed;

public:
	static void    Initialise(Const char *pWaterDat); // out of class in III PC and later because of SecuROM
	static void    Shutdown();
	static void    CreateWavyAtomic();
	static void    DestroyWavyAtomic();
	static void    AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel);
	static bool    WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel = nil);
	static bool    TestVisibilityForFineWaterBlocks(const CVector &worldPos);
	static void    RemoveIsolatedWater();
	static bool    GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ);
	static bool    GetWaterLevel(CVector coors, float *pfOutLevel, bool bDontCheckZ) { return GetWaterLevel(coors.x, coors.y, coors.z, pfOutLevel, 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();
};


code:
#include "common.h"

#include "Weather.h"

#include "Camera.h"
#include "Clock.h"
#include "CutsceneMgr.h"
#include "DMAudio.h"
#include "General.h"
#include "Pad.h"
#include "Particle.h"
#include "RenderBuffer.h"
#include "Stats.h"
#include "Shadows.h"
#include "Timecycle.h"
#include "Timer.h"
#include "Vehicle.h"
#include "World.h"
#include "ZoneCull.h"

int32 CWeather::SoundHandle = -1;

int32 CWeather::WeatherTypeInList;
int16 CWeather::OldWeatherType;
int16 CWeather::NewWeatherType;
int16 CWeather::ForcedWeatherType;

bool CWeather::LightningFlash;
bool CWeather::LightningBurst;
uint32 CWeather::LightningStart;
uint32 CWeather::LightningFlashLastChange;
uint32 CWeather::WhenToPlayLightningSound;
uint32 CWeather::LightningDuration;

float CWeather::Foggyness;
float CWeather::CloudCoverage;
float CWeather::Wind;
float CWeather::Rain;
float CWeather::InterpolationValue;
float CWeather::WetRoads;
float CWeather::Rainbow;

bool CWeather::bScriptsForceRain;
bool CWeather::Stored_StateStored;

float CWeather::Stored_InterpolationValue;
int16 CWeather::Stored_OldWeatherType;
int16 CWeather::Stored_NewWeatherType;
float CWeather::Stored_Rain;

tRainStreak Streaks[NUM_RAIN_STREAKS];

const int16 WeatherTypesList[] = {
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY,
	WEATHER_CLOUDY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_CLOUDY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_CLOUDY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_CLOUDY, WEATHER_CLOUDY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY,
	WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_CLOUDY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_SUNNY,
	WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_RAINY, WEATHER_CLOUDY,
};

const float Windyness[] = {
	0.0f, // WEATHER_SUNNY
	0.7f, // WEATHER_CLOUDY
	1.0f, // WEATHER_RAINY
	0.5f  // WEATHER_FOGGY
};

#define MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES (50)

#define RAIN_CHANGE_SPEED (0.003f)

#define DROPLETS_LEFT_OFFSET (10.0f)
#define DROPLETS_RIGHT_OFFSET (10.0f)
#define DROPLETS_TOP_OFFSET (10.0f)
#define DROPLETS_BOTTOM_OFFSET (10.0f)

#define STREAK_U (10.0f)
#define STREAK_V (18.0f)
#define LARGE_STREAK_COEFFICIENT (1.23f)
#define STREAK_MIN_DISTANCE (8.0f)
#define STREAK_MAX_DISTANCE (16.0f)

#define SPLASH_CHECK_RADIUS (7.0f)
#define SPLASH_OFFSET_RADIUS (2.0f)

#define STREAK_LIFETIME (4.0f)
#define STREAK_INTEROLATION_TIME (0.3f)

#define RAIN_COLOUR_R (200)
#define RAIN_COLOUR_G (200)
#define RAIN_COLOUR_B (256)
#define RAIN_ALPHA (255)

void CWeather::Init(void)
{
	NewWeatherType = WEATHER_SUNNY;
	bScriptsForceRain = false;
	OldWeatherType = WEATHER_CLOUDY;
	Stored_StateStored = false;
	InterpolationValue = 0.0f;
	WhenToPlayLightningSound = 0;
	WeatherTypeInList = 0;
	ForcedWeatherType = WEATHER_RANDOM;
	SoundHandle = DMAudio.CreateEntity(AUDIOTYPE_WEATHER, (void*)1);
	if (SoundHandle >= 0)
		DMAudio.SetEntityStatus(SoundHandle, TRUE);
}

void CWeather::Update(void)
{
	float fNewInterpolation = CClock::GetMinutes() * 1.0f / 60;
	if (fNewInterpolation < InterpolationValue) {
		// new hour
		OldWeatherType = NewWeatherType;
		if (ForcedWeatherType >= 0)
			NewWeatherType = ForcedWeatherType;
		else {
			WeatherTypeInList = (WeatherTypeInList + 1) % ARRAY_SIZE(WeatherTypesList);
			NewWeatherType = WeatherTypesList[WeatherTypeInList];
#ifdef FIX_BUGS
		}
		if (NewWeatherType == WEATHER_RAINY)
			CStats::mmRain += CGeneral::GetRandomNumber() & 7;
#else
			if (NewWeatherType == WEATHER_RAINY)
				CStats::mmRain += CGeneral::GetRandomNumber() & 7;
		}
#endif
	}
	InterpolationValue = fNewInterpolation;
	if (CPad::GetPad(1)->GetRightShockJustDown()) {
		NewWeatherType = (NewWeatherType + 1) % WEATHER_TOTAL;
		OldWeatherType = NewWeatherType;
	}

	// Lightning
	if (NewWeatherType != WEATHER_RAINY || OldWeatherType != WEATHER_RAINY) {
		LightningFlash = false;
		LightningBurst = false;
	}
	else{
		if (LightningBurst) {
			if ((CGeneral::GetRandomNumber() & 255) >= 32) {
				// 0.875 probability
				if (CTimer::GetTimeInMilliseconds() - LightningFlashLastChange > MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES) {
					bool bOldLightningFlash = LightningFlash;
					LightningFlash = CGeneral::GetRandomTrueFalse();
					if (LightningFlash != bOldLightningFlash)
						LightningFlashLastChange = CTimer::GetTimeInMilliseconds();
				}
			}
			else {
				// 0.125 probability
				LightningBurst = false;
				LightningDuration = Min(CTimer::GetFrameCounter() - LightningStart, 20);
				LightningFlash = false;
				WhenToPlayLightningSound = CTimer::GetTimeInMilliseconds() + 150 * (20 - LightningDuration);
			}
		}
		else {
			if (CGeneral::GetRandomNumber() >= 200) {
				// lower probability on PC due to randomness bug
				LightningFlash = false;
			}
			else {
				LightningBurst = true;
				LightningStart = CTimer::GetFrameCounter();
				LightningFlashLastChange = CTimer::GetTimeInMilliseconds();
				LightningFlash = true;
			}
		}
	}
	if (WhenToPlayLightningSound && CTimer::GetTimeInMilliseconds() > WhenToPlayLightningSound) {
		DMAudio.PlayOneShot(SoundHandle, SOUND_LIGHTNING, LightningDuration);
		CPad::GetPad(0)->StartShake(40 * LightningDuration + 100, 2 * LightningDuration + 80);
		WhenToPlayLightningSound = 0;
	}

	// Wet roads
	if (OldWeatherType == WEATHER_RAINY) {
		if (NewWeatherType == WEATHER_RAINY)
			WetRoads = 1.0f;
		else
			WetRoads = 1.0f - InterpolationValue;
	}
	else {
		if (NewWeatherType == WEATHER_RAINY)
			WetRoads = InterpolationValue;
		else
			WetRoads = 0.0f;
	}

	// Rain
#ifndef VC_RAIN_NERF
	float fNewRain;
	if (NewWeatherType == WEATHER_RAINY) {
		// if raining for >1 hour, values: 0, 0.33, 0.66, 0.99, switching every ~16.5s
		fNewRain = ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.33f;
		if (OldWeatherType != WEATHER_RAINY) {
			if (InterpolationValue < 0.4f)
				// if rain has just started (<24 minutes), always 0.5
				fNewRain = 0.5f;
			else
				// if rain is ongoing for >24 minutes, values: 0.25, 0.5, 0.75, 1.0, switching every ~16.5s
				fNewRain = 0.25f + ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.25f;
		}
	}
	else
		fNewRain = 0.0f;
	if (Rain != fNewRain) { // ok to use comparasion
		if (Rain < fNewRain)
			Rain = Min(fNewRain, Rain + RAIN_CHANGE_SPEED * CTimer::GetTimeStep());
		else
			Rain = Max(fNewRain, Rain - RAIN_CHANGE_SPEED * CTimer::GetTimeStep());
	}
#else
	float fNewRain;
	if (NewWeatherType == WEATHER_RAINY) {
		// if raining for >1 hour, values: 0, 0.33, switching every ~16.5s
		fNewRain = (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.33f;
		if (OldWeatherType != WEATHER_RAINY) {
			if (InterpolationValue < 0.4f)
				// if rain has just started (<24 minutes), always 0.5
				fNewRain = 0.5f;
			else
				// if rain is ongoing for >24 minutes, values: 0.25, 0.5, switching every ~16.5s
				fNewRain = 0.25f + (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.25f;
		}
		fNewRain = Max(fNewRain, 0.5f);
	}
	else
		fNewRain = 0.0f;
	Rain = fNewRain;
#endif

	// Clouds
	if (OldWeatherType != WEATHER_SUNNY)
		CloudCoverage = 1.0f - InterpolationValue;
	else
		CloudCoverage = 0.0f;
	if (NewWeatherType != WEATHER_SUNNY)
		CloudCoverage += InterpolationValue;

	// Fog
	if (OldWeatherType == WEATHER_FOGGY)
		Foggyness = 1.0f - InterpolationValue;
	else
		Foggyness = 0.0f;
	if (NewWeatherType == WEATHER_FOGGY)
		Foggyness += InterpolationValue;
	if (OldWeatherType == WEATHER_RAINY && NewWeatherType == WEATHER_SUNNY && InterpolationValue < 0.5f && CClock::GetHours() > 6 && CClock::GetHours() < 21)
		Rainbow = 1.0f - 4.0f * Abs(InterpolationValue - 0.25f) / 4.0f;
	else
		Rainbow = 0.0f;
	Wind = InterpolationValue * Windyness[NewWeatherType] + (1.0f - InterpolationValue) * Windyness[OldWeatherType];
	AddRain();
}

void CWeather::ForceWeather(int16 weather)
{
	ForcedWeatherType = weather;
}

void CWeather::ForceWeatherNow(int16 weather)
{
	OldWeatherType = weather;
	NewWeatherType = weather;
	ForcedWeatherType = weather;
}

void CWeather::ReleaseWeather()
{
	ForcedWeatherType = -1;
}

void CWeather::AddRain()
{
	if (CCullZones::CamNoRain() || CCullZones::PlayerNoRain())
		return;
	if (TheCamera.GetLookingLRBFirstPerson()) {
		CVehicle* pVehicle = FindPlayerVehicle();
		if (pVehicle && pVehicle->CarHasRoof()) {
			CParticle::RemovePSystem(PARTICLE_RAINDROP_2D);
			return;
		}
	}
	if (Rain <= 0.1f)
		return;
	static RwRGBA colour;
	float screen_width = SCREEN_WIDTH;
	float screen_height = SCREEN_HEIGHT;
	int cur_frame = (int)(3 * Rain) & 3;
	int num_drops = (int)(2 * Rain) + 2;
	static int STATIC_RAIN_ANGLE = -45;
	static int count = 1500;
	static int add_angle = 1;
	if (--count == 0) {
		count = 1;
		if (add_angle) {
			STATIC_RAIN_ANGLE += 12;
			if (STATIC_RAIN_ANGLE > 45) {
				count = 1500;
				add_angle = !add_angle;
			}
		}
		else {
			STATIC_RAIN_ANGLE -= 12;
			if (STATIC_RAIN_ANGLE < -45) {
				count = 1500;
				add_angle = !add_angle;
			}
		}
	}
	float rain_angle = DEGTORAD(STATIC_RAIN_ANGLE + ((STATIC_RAIN_ANGLE < 0) ? 360 : 0));
	float sin_angle = Sin(rain_angle);
	float cos_angle = Cos(rain_angle);
	float base_x = 0.0f * cos_angle - 1.0f * sin_angle;
	float base_y = 1.0f * cos_angle + 0.0f * sin_angle;
	CVector xpos(0.0f, 0.0f, 0.0f);
	for (int i = 0; i < 2 * num_drops; i++) {
		CVector dir;
		dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.z = 0;
		CParticle::AddParticle(PARTICLE_RAINDROP_2D, xpos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f),
			colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame);
		xpos.x += screen_width / (2 * num_drops);
		xpos.x += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f);
	}
	CVector ypos(0.0f, 0.0f, 0.0f);
	for (int i = 0; i < num_drops; i++) {
		CVector dir;
		dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.z = 0;
		CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f),
			colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame);
		ypos.y += screen_width / num_drops;
		ypos.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f);
	}
	CVector ypos2(0.0f, 0.0f, 0.0f);
	for (int i = 0; i < num_drops; i++) {
		CVector dir;
		dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f);
		dir.z = 0;
		CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos2, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f),
			colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame);
		ypos2.y += screen_width / num_drops;
		ypos2.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f);
	}
	for (int i = 0; i < num_drops; i++) {
		CVector pos;
		pos.x = CGeneral::GetRandomNumberInRange(DROPLETS_LEFT_OFFSET, screen_width - DROPLETS_RIGHT_OFFSET);
		pos.y = CGeneral::GetRandomNumberInRange(DROPLETS_TOP_OFFSET, screen_height - DROPLETS_TOP_OFFSET);
		pos.z = 0.0f;
		CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f),
			colour, CGeneral::GetRandomNumberInRange(-10, 10), 360 - rain_angle + CGeneral::GetRandomNumberInRange(-30, 30), cur_frame, 50);
	}
	int num_splash_attempts = (int)(3 * Rain) + 1;
	int num_splashes = (int)(3 * Rain) + 4;
	CVector splash_points[4];
	splash_points[0] = CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) *
		RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude();
	splash_points[1] = CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) *
		RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude();
	splash_points[2] = 4.0f * CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) *
		RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude();
	splash_points[3] = 4.0f * CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) *
		RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude();
	RwV3dTransformPoints(splash_points, splash_points, 4, RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)));
	CVector fp = (splash_points[0] + splash_points[1] + splash_points[2] + splash_points[3]) / 4;
	for (int i = 0; i < num_splash_attempts; i++) {
		CColPoint point;
		CEntity* entity;
		CVector np = fp + CVector(CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), 0.0f);
		if (CWorld::ProcessVerticalLine(np + CVector(0.0f, 0.0f, 40.0f), -40.0f, point, entity, true, false, false, false, true, false, nil)) {
			for (int j = 0; j < num_splashes; j++)
				CParticle::AddParticle((CGeneral::GetRandomTrueFalse() ? PARTICLE_RAIN_SPLASH : PARTICLE_RAIN_SPLASHUP),
					CVector(
						np.x + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS),
						np.y + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS),
						point.point.z + 0.1f),
					CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colour);
		}
	}
}

void RenderOneRainStreak(CVector pos, CVector unused, int intensity, bool scale, float distance)
{
	static float RandomTex;
	static float RandomTexX;
	static float RandomTexY;
	TempBufferRenderIndexList[TempBufferIndicesStored + 0] = TempBufferVerticesStored + 0;
	TempBufferRenderIndexList[TempBufferIndicesStored + 1] = TempBufferVerticesStored + 2;
	TempBufferRenderIndexList[TempBufferIndicesStored + 2] = TempBufferVerticesStored + 1;
	TempBufferRenderIndexList[TempBufferIndicesStored + 3] = TempBufferVerticesStored + 0;
	TempBufferRenderIndexList[TempBufferIndicesStored + 4] = TempBufferVerticesStored + 3;
	TempBufferRenderIndexList[TempBufferIndicesStored + 5] = TempBufferVerticesStored + 2;
	TempBufferRenderIndexList[TempBufferIndicesStored + 6] = TempBufferVerticesStored + 1;
	TempBufferRenderIndexList[TempBufferIndicesStored + 7] = TempBufferVerticesStored + 2;
	TempBufferRenderIndexList[TempBufferIndicesStored + 8] = TempBufferVerticesStored + 4;
	TempBufferRenderIndexList[TempBufferIndicesStored + 9] = TempBufferVerticesStored + 2;
	TempBufferRenderIndexList[TempBufferIndicesStored + 10] = TempBufferVerticesStored + 3;
	TempBufferRenderIndexList[TempBufferIndicesStored + 11] = TempBufferVerticesStored + 4;
	RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0, 0, 0, 0);
	RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 0], pos.x + 11.0f * TheCamera.GetUp().x, pos.y + 11.0f * TheCamera.GetUp().y, pos.z + 11.0f * TheCamera.GetUp().z);
	RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 1], 0, 0, 0, 0);
	RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 1], pos.x - 9.0f * TheCamera.GetRight().x, pos.y - 9.0f * TheCamera.GetRight().y, pos.z - 9.0f * TheCamera.GetRight().z);
	RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RAIN_COLOUR_R * intensity / 256, RAIN_COLOUR_G * intensity / 256, RAIN_COLOUR_B * intensity / 256, RAIN_ALPHA);
	RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 2], pos.x, pos.y, pos.z);
	RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 3], 0, 0, 0, 0);
	RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 3], pos.x + 9.0f * TheCamera.GetRight().x, pos.y + 9.0f * TheCamera.GetRight().y, pos.z + 9.0f * TheCamera.GetRight().z);
	RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0, 0, 0, 0);
	RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 4], pos.x - 11.0f * TheCamera.GetUp().x, pos.y - 11.0f * TheCamera.GetUp().y, pos.z - 11.0f * TheCamera.GetUp().z);
	float u = STREAK_U;
	float v = STREAK_V;
	if (scale) {
		u *= LARGE_STREAK_COEFFICIENT;
		v *= LARGE_STREAK_COEFFICIENT;
	}
	float distance_coefficient;
	if (distance < STREAK_MIN_DISTANCE)
		distance_coefficient = 1.0f;
	else if (distance > STREAK_MAX_DISTANCE)
		distance_coefficient = 0.5f;
	else
		distance_coefficient = 1.0f - 0.5f * (distance - STREAK_MIN_DISTANCE) / (STREAK_MAX_DISTANCE - STREAK_MIN_DISTANCE);
	u *= distance_coefficient;
	v *= distance_coefficient;
	if (!CTimer::GetIsPaused()) {
		RandomTex = ((CGeneral::GetRandomNumber() & 255) - 128) * 0.01f;
		RandomTexX = (CGeneral::GetRandomNumber() & 127) * 0.01f;
		RandomTexY = (CGeneral::GetRandomNumber() & 127) * 0.01f;
	}
	RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0.5f * u - RandomTex + RandomTexX);
	RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 0], -v * 0.5f + RandomTexY);
	RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexX);
	RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexY);
	RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 2], 0.5f * u + RandomTexX);
	RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RandomTexY);
	RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 3], u + RandomTexX);
	RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 3], RandomTexY);
	RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0.5f * u + RandomTex + RandomTexX);
	RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 5], 0.5f * v + RandomTexY);
	TempBufferIndicesStored += 12;
	TempBufferVerticesStored += 5;
}

void CWeather::RenderRainStreaks(void)
{
	if (CTimer::GetIsCodePaused())
		return;
	int base_intensity = (64.0f - CTimeCycle::GetFogReduction()) / 64.0f * int(255 * Rain);
	if (base_intensity == 0)
		return;
	TempBufferIndicesStored = 0;
	TempBufferVerticesStored = 0;
	for (int i = 0; i < NUM_RAIN_STREAKS; i++) {
		if (Streaks[i].timer) {
			float secondsElapsed = (CTimer::GetTimeInMilliseconds() - Streaks[i].timer) / 1024.0f;
			if (secondsElapsed > STREAK_LIFETIME)
				Streaks[i].timer = 0;
			else{
				int intensity;
				if (secondsElapsed < STREAK_INTEROLATION_TIME)
					intensity = base_intensity * 0.5f * secondsElapsed / STREAK_INTEROLATION_TIME;
				else if (secondsElapsed > (STREAK_LIFETIME - STREAK_INTEROLATION_TIME))
					intensity = (STREAK_LIFETIME - secondsElapsed) * 0.5f * base_intensity / STREAK_INTEROLATION_TIME;
				else
					intensity = base_intensity * 0.5f;
				CVector dir = Streaks[i].direction;
				dir.Normalise();
				CVector pos = Streaks[i].position + secondsElapsed * Streaks[i].direction;
				RenderOneRainStreak(pos, dir, intensity, false, (pos - TheCamera.GetPosition()).Magnitude());
#ifndef FIX_BUGS // remove useless code
				if (secondsElapsed > 1.0f && secondsElapsed < STREAK_LIFETIME - 1.0f) {
					CGeneral::GetRandomNumber(), CGeneral::GetRandomNumber();
				}
#endif
			}
		}
		else if ((CGeneral::GetRandomNumber() & 0xF00) == 0){
			// 1/16 probability
			Streaks[i].direction = CVector(4.0f, 4.0f, -4.0f);
			Streaks[i].position = 6.0f * TheCamera.GetForward() + TheCamera.GetPosition() + CVector(-1.8f * Streaks[i].direction.x, -1.8f * Streaks[i].direction.y, 8.0f);
			if (!CCutsceneMgr::IsRunning()) {
				Streaks[i].position.x += 2.0f * FindPlayerSpeed().x * 60.0f;
				Streaks[i].position.y += 2.0f * FindPlayerSpeed().y * 60.0f;
			}
			else
				Streaks[i].position += (TheCamera.GetPosition() - TheCamera.m_RealPreviousCameraPosition) * 20.0f;
			Streaks[i].position.x += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f;
			Streaks[i].position.y += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f;
			Streaks[i].timer = CTimer::GetTimeInMilliseconds();
		}
	}
	if (TempBufferIndicesStored){
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
		RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
		RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
		RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR);
		RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
		RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
		RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRainDropTex[3]));
		if (RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, 1))
		{
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
			RwIm3DEnd();
		}
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
		RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
		RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
		RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
		RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
	}
	TempBufferVerticesStored = 0;
	TempBufferIndicesStored = 0;
}

void CWeather::StoreWeatherState()
{
	Stored_StateStored = true;
	Stored_InterpolationValue = InterpolationValue;
	Stored_Rain = Rain;
	Stored_NewWeatherType = NewWeatherType;
	Stored_OldWeatherType = OldWeatherType;
}

void CWeather::RestoreWeatherState()
{
#ifdef FIX_BUGS // it's not used anyway though
	Stored_StateStored = false;
#endif
	InterpolationValue = Stored_InterpolationValue;
	Rain = Stored_Rain;
	NewWeatherType = Stored_NewWeatherType;
	OldWeatherType = Stored_OldWeatherType;
}


code:
enum {
	WEATHER_SUNNY,
	WEATHER_CLOUDY,
	WEATHER_RAINY,
	WEATHER_FOGGY
};

class CWeather
{
public:
	enum {
		WEATHER_RANDOM = -1,
		WEATHER_SUNNY = 0,
		WEATHER_CLOUDY = 1,
		WEATHER_RAINY = 2,
		WEATHER_FOGGY = 3,
		WEATHER_TOTAL = 4
	};
	static int32 SoundHandle;

	static int32 WeatherTypeInList;
	static int16 OldWeatherType;
	static int16 NewWeatherType;
	static int16 ForcedWeatherType;

	static bool LightningFlash;
	static bool LightningBurst;
	static uint32 LightningStart;
	static uint32 LightningFlashLastChange;
	static uint32 WhenToPlayLightningSound;
	static uint32 LightningDuration;

	static float Foggyness;
	static float CloudCoverage;
	static float Wind;
	static float Rain;
	static float InterpolationValue;
	static float WetRoads;
	static float Rainbow;

	static bool bScriptsForceRain;
	static bool Stored_StateStored;
	static float Stored_InterpolationValue;
	static int16 Stored_OldWeatherType;
	static int16 Stored_NewWeatherType;
	static float Stored_Rain;

	static void RenderRainStreaks(void);
	static void Update(void);
	static void Init(void);

	static void ReleaseWeather();
	static void ForceWeather(int16);
	static void ForceWeatherNow(int16);
	static void StoreWeatherState();
	static void RestoreWeatherState();
	static void AddRain();
};

enum {
	NUM_RAIN_STREAKS = 35
};

struct tRainStreak
{
	CVector position;
	CVector direction;
	uint32 timer;
};

extern RwTexture* gpRainDropTex[4];


code:
#include "common.h"

struct rpGeometryList
{
	RpGeometry **geometries;
	int32 numGeoms;
};

struct rpAtomicBinary
{
	RwInt32 frameIndex;
	RwInt32 geomIndex;
	RwInt32 flags;
	RwInt32 unused;
};

static int32 numberGeometrys;
static int32 streamPosition;
static rpGeometryList gGeomList;
static rwFrameList gFrameList;
static RpClumpChunkInfo gClumpInfo;

rpGeometryList*
GeometryListStreamRead1(RwStream *stream, rpGeometryList *geomlist)
{
	int i;
	RwUInt32 size, version;
	RwInt32 numGeoms;

	numberGeometrys = 0;
	if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
		return nil;
	assert(size == 4);
	if(RwStreamRead(stream, &numGeoms, 4) != 4)
		return nil;

	numberGeometrys = numGeoms/2;
	geomlist->numGeoms = numGeoms;
	if(geomlist->numGeoms > 0){
		geomlist->geometries = (RpGeometry**)RwMalloc(geomlist->numGeoms * sizeof(RpGeometry*));
		if(geomlist->geometries == nil)
			return nil;
		memset(geomlist->geometries, 0, geomlist->numGeoms * sizeof(RpGeometry*));
	}else
		geomlist->geometries = nil;

	for(i = 0; i < numberGeometrys; i++){
		if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version))
			return nil;
		geomlist->geometries[i] = RpGeometryStreamRead(stream);
		if(geomlist->geometries[i] == nil)
			return nil;
	}

	return geomlist;
}

rpGeometryList*
GeometryListStreamRead2(RwStream *stream, rpGeometryList *geomlist)
{
	int i;
	RwUInt32 version;

	for(i = numberGeometrys; i < geomlist->numGeoms; i++){
		if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version))
			return nil;
		geomlist->geometries[i] = RpGeometryStreamRead(stream);
		if(geomlist->geometries[i] == nil)
			return nil;
	}

	return geomlist;
}

void
GeometryListDeinitialize(rpGeometryList *geomlist)
{
	int i;

	for(i = 0; i < geomlist->numGeoms; i++)
		if(geomlist->geometries[i])
			RpGeometryDestroy(geomlist->geometries[i]);

	if(geomlist->numGeoms){
		RwFree(geomlist->geometries);
		geomlist->numGeoms = 0;
	}
}

RpAtomic*
ClumpAtomicStreamRead(RwStream *stream, rwFrameList *frmList, rpGeometryList *geomList)
{
	RwUInt32 size, version;
	rpAtomicBinary a;
	RpAtomic *atomic;

	numberGeometrys = 0;
	if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
		return nil;
	assert(size <= sizeof(rpAtomicBinary));
	if(RwStreamRead(stream, &a, size) != size)
		return nil;

	atomic = RpAtomicCreate();
	if(atomic == nil)
		return nil;

	RpAtomicSetFlags(atomic, a.flags);

	if(frmList->numFrames){
		assert(a.frameIndex < frmList->numFrames);
		RpAtomicSetFrame(atomic, frmList->frames[a.frameIndex]);
	}

	if(geomList->numGeoms){
		assert(a.geomIndex < geomList->numGeoms);
		RpAtomicSetGeometry(atomic, geomList->geometries[a.geomIndex], 0);
	}else{
		RpGeometry *geom;
		if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)){
			RpAtomicDestroy(atomic);
			return nil;
		}
		geom = RpGeometryStreamRead(stream);
		if(geom == nil){
			RpAtomicDestroy(atomic);
			return nil;
		}
		RpAtomicSetGeometry(atomic, geom, 0);
		RpGeometryDestroy(geom);
	}

	return atomic;
}

bool
RpClumpGtaStreamRead1(RwStream *stream)
{
	RwUInt32 size, version;

	if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
		return false;
	if(version >= 0x33000){
		assert(size == 12);
		if(RwStreamRead(stream, &gClumpInfo, 12) != 12)
			return false;
	}else{
		assert(size == 4);
		if(RwStreamRead(stream, &gClumpInfo, 4) != 4)
			return false;
	}

	if(!RwStreamFindChunk(stream, rwID_FRAMELIST, nil, &version))
		return false;
	if(rwFrameListStreamRead(stream, &gFrameList) == nil)
		return false;

	if(!RwStreamFindChunk(stream, rwID_GEOMETRYLIST, nil, &version)){
		rwFrameListDeinitialize(&gFrameList);
		return false;
	}
	if(GeometryListStreamRead1(stream, &gGeomList) == nil){
		rwFrameListDeinitialize(&gFrameList);
		return false;
	}
	streamPosition = STREAMPOS(stream);
	return true;
}

RpClump*
RpClumpGtaStreamRead2(RwStream *stream)
{
	int i;
	RwUInt32 version;
	RpAtomic *atomic;
	RpClump *clump;

	clump = RpClumpCreate();
	if(clump == nil)
		return nil;

	RwStreamSkip(stream, streamPosition - STREAMPOS(stream));

	if(GeometryListStreamRead2(stream, &gGeomList) == nil){
		GeometryListDeinitialize(&gGeomList);
		rwFrameListDeinitialize(&gFrameList);
		RpClumpDestroy(clump);
		return nil;
	}

	RpClumpSetFrame(clump, gFrameList.frames[0]);

	for(i = 0; i < gClumpInfo.numAtomics; i++){
		if(!RwStreamFindChunk(stream, rwID_ATOMIC, nil, &version)){
			GeometryListDeinitialize(&gGeomList);
			rwFrameListDeinitialize(&gFrameList);
			RpClumpDestroy(clump);
			return nil;
		}

		atomic = ClumpAtomicStreamRead(stream, &gFrameList, &gGeomList);
		if(atomic == nil){
			GeometryListDeinitialize(&gGeomList);
			rwFrameListDeinitialize(&gFrameList);
			RpClumpDestroy(clump);
			return nil;
		}

		RpClumpAddAtomic(clump, atomic);
	}

	GeometryListDeinitialize(&gGeomList);
	rwFrameListDeinitialize(&gFrameList);
	return clump;
}

void
RpClumpGtaCancelStream(void)
{
	GeometryListDeinitialize(&gGeomList);
	rwFrameListDeinitialize(&gFrameList);
	gFrameList.numFrames = 0;
}