summaryrefslogblamecommitdiffstats
path: root/src/render/SpecialFX.cpp
blob: 4c69f7493320ad0fc3c607a6a6bc8dd25b5a01ef (plain) (tree)
1
2
3
4
5
6
7
8
                   
 
                      
                         



                   


                       
                      
                   
                         

                              
                      
                     
                    

                    

                    


                     
                    
 
                               
                                    
 
                               


                                                                                                                                 
 

                           
                                 
                                 

                                      
 




                              















                                                 







                                                   












                                
                                   
                              
                            

                               








                                                                     

 
























                                                                                                  
                               




                                                                                                                        
















                                                                                                 

                                     





                               



                                                      




                        





                                     

 






                                                                            
                                                                     


                                                       
                                           



















                                                                                                                                          
                                                                     


                                                           
                                           

























                                                                                                                                             
                                                                                                  









                                                                                                                                                                      

                                                                          




































                                                                                                            



                                                                                                     




















                                             



                                                                                                 
 











                                                                 
 
                         
                                               

                                          
 











                                         


                                

                                     
                                            
                                            



                                                                                              

                                                                                                                                              












                                                                                                    


                                                     
 

                              
                                                 


                                            
                                                                                                             
 


















                                                                               

                                                         






                                                                                    













                                                                                                          
                                  
                                            


















                                                                                                                                

                                                                        




























                                                                                                                     
         



                                







                                                                                                      
                                                                                               
 



                                                                                                  
 








                                                                                                                             
 








                                                                                                        
 





































                                                                                                                              
 

























































                                                                                                                              




                                                                            



                                
                                                   






                                            
                                                                             
                                 
 
 




                                            

 

                                         

































                                                                                                                                                                






                               


                          
                                        




                                            


                        




                                     
                                                  















                                                                          

 


                                                     
 






                                                               
                                                                  





















                                                                                                                 

 











                                                               
 
 






                                                  
                                                                        
                                                           



                                                                                                                                                                
                                                                                                                                                                               

                                 





                                                                
 
 
           

                                                                                                                                                                                
                           
                                                         
                      
                                                              











































                                                                                                                                                                                                         
                        











                                                                                                                                                                                 
                                             
                                                                       
                                                  





                                                                                                            








                                                                                                                            







                                                                                                       
                                          







                                                                                                                    

                                                            









                                                                                                    
                









                                                                                                                  


                       





                                                                                                                                                                           

 
 



                    
 
 






                                                                      






                            










                                                                                               






                                                       



























                                                                                                         


                           


                    








                                                                            


                                     
                                             


































                                                                                                                        

                                                                       

                               
                              
      





                                                                                                                                            

                                                                                                                          



                                                                                                                                              

                 































                                                                                                                                         
 














                                                                                                                                 
 















                                                                                                                                   

















                                                                                                                                 

                                                                               









                                                                                                                                         










                                                                       








                                                                                                                            
 
 

                                                   






                          






































                                                                                            













                                                                      



























































                                                                                                                                                                  


                                                                        
                                                                            




                                          








                                                                                                                            

 

 

                                      






                                                                               


                                                     







                                                                                                                                

                                                                        





                                                                                                                               
                                                   
















































































































                                                                                                                                   
#include "common.h"

#include "SpecialFX.h"
#include "RenderBuffer.h"
#include "Timer.h"
#include "Sprite.h"
#include "Font.h"
#include "Text.h"
#include "TxdStore.h"
#include "FileMgr.h"
#include "FileLoader.h"
#include "Timecycle.h"
#include "Lights.h"
#include "ModelIndices.h"
#include "VisibilityPlugins.h"
#include "World.h"
#include "PlayerPed.h"
#include "Particle.h"
#include "Shadows.h"
#include "General.h"
#include "Camera.h"
#include "Shadows.h"
#include "main.h"
#include "ColStore.h"
#include "Coronas.h"
#include "Script.h"
#include "DMAudio.h"

RwIm3DVertex StreakVertices[4];
RwImVertexIndex StreakIndexList[12];

RwIm3DVertex TraceVertices[10];
static RwImVertexIndex TraceIndexList[48] = {0, 5, 7, 0, 7, 2, 0, 7, 5, 0, 2, 7, 0, 4, 9, 0,
										9, 5, 0, 9, 4, 0, 5, 9, 0, 1, 6, 0, 6, 5, 0, 6,
										1, 0, 5, 6, 0, 3, 8, 0, 8, 5, 0, 8, 3, 0, 5, 8 };

bool CSpecialFX::bVideoCam;
bool CSpecialFX::bLiftCam;
bool CSpecialFX::bSnapShotActive;
int32 CSpecialFX::SnapShotFrames;
static RwTexture* gpSmokeTrailTexture;


void
CSpecialFX::Init(void)
{
	CBulletTraces::Init();

	RwIm3DVertexSetU(&TraceVertices[0], 0.0);
	RwIm3DVertexSetV(&TraceVertices[0], 0.0);
	RwIm3DVertexSetU(&TraceVertices[1], 1.0);
	RwIm3DVertexSetV(&TraceVertices[1], 0.0);
	RwIm3DVertexSetU(&TraceVertices[2], 1.0);
	RwIm3DVertexSetV(&TraceVertices[2], 0.0);
	RwIm3DVertexSetU(&TraceVertices[3], 1.0);
	RwIm3DVertexSetV(&TraceVertices[3], 0.0);
	RwIm3DVertexSetU(&TraceVertices[4], 1.0);
	RwIm3DVertexSetV(&TraceVertices[4], 0.0);
	RwIm3DVertexSetU(&TraceVertices[5], 0.0);
	RwIm3DVertexSetU(&TraceVertices[6], 1.0);
	RwIm3DVertexSetU(&TraceVertices[7], 1.0);
	RwIm3DVertexSetU(&TraceVertices[8], 1.0);
	RwIm3DVertexSetU(&TraceVertices[9], 1.0);

	RwIm3DVertexSetU(&StreakVertices[0], 0.0f);
	RwIm3DVertexSetV(&StreakVertices[0], 0.0f);
	RwIm3DVertexSetU(&StreakVertices[1], 1.0f);
	RwIm3DVertexSetV(&StreakVertices[1], 0.0f);
	RwIm3DVertexSetU(&StreakVertices[2], 0.0f);
	RwIm3DVertexSetV(&StreakVertices[2], 0.0f);
	RwIm3DVertexSetU(&StreakVertices[3], 1.0f);
	RwIm3DVertexSetV(&StreakVertices[3], 0.0f);
	StreakIndexList[0] = 0;
	StreakIndexList[1] = 1;
	StreakIndexList[2] = 2;
	StreakIndexList[3] = 1;
	StreakIndexList[4] = 3;
	StreakIndexList[5] = 2;
	StreakIndexList[6] = 0;
	StreakIndexList[7] = 2;
	StreakIndexList[8] = 1;
	StreakIndexList[9] = 1;
	StreakIndexList[10] = 2;
	StreakIndexList[11] = 3;

	CMotionBlurStreaks::Init();
	CBrightLights::Init();
	CShinyTexts::Init();
	CMoneyMessages::Init();
	C3dMarkers::Init();
	CSpecialFX::bSnapShotActive = false;
	CSpecialFX::bVideoCam = false;
	CSpecialFX::SnapShotFrames = 0;
	CSpecialFX::bLiftCam = false;
	CTxdStore::PushCurrentTxd();
	CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle"));
	if(gpSmokeTrailTexture == nil)
		gpSmokeTrailTexture = RwTextureRead("smoketrail", 0);
	CTxdStore::PopCurrentTxd();
}

void
CSpecialFX::AddWeaponStreak(int type)
{
	static CMatrix matrix;
	CVector start;
	CVector end;

	if (FindPlayerPed() != nil && FindPlayerPed()->m_pWeaponModel != nil) {
		switch (type) {
		case WEAPONTYPE_BASEBALLBAT:
			matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel));
			start = matrix * CVector(0.02f, 0.05f, 0.07f);
			end = matrix * CVector(0.246f, 0.0325f, 0.796f);
			break;
		case WEAPONTYPE_GOLFCLUB:
			matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel));
			start = matrix * CVector(0.02f, 0.05f, 0.07f);
			end = matrix * CVector(-0.054f, 0.0325f, 0.796f);
			break;
		case WEAPONTYPE_KATANA:
			matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel));
			start = matrix * CVector(0.02f, 0.05f, 0.07f);
			end = matrix * CVector(0.096f, -0.0175f, 1.096f);
			break;
		default:
			return;
		}
		CMotionBlurStreaks::RegisterStreak((uintptr)FindPlayerPed()->m_pWeaponModel, 100, 100, 100, start, end);
	}
}

RwObject*
LookForBatCB(RwObject *object, void *data)
{
	static CMatrix MatLTM;

	if(CVisibilityPlugins::GetAtomicModelInfo((RpAtomic*)object) == (CSimpleModelInfo*)data){
		MatLTM = CMatrix(RwFrameGetLTM(RpAtomicGetFrame((RpAtomic*)object)));
		CVector p1 = MatLTM * CVector(0.02f, 0.05f, 0.07f);
		CVector p2 = MatLTM * CVector(0.246f, 0.0325f, 0.796f);
		CMotionBlurStreaks::RegisterStreak((uintptr)object, 100, 100, 100, p1, p2);
	}
	return nil;
}

void
CSpecialFX::Update(void)
{
	CMotionBlurStreaks::Update();
	CBulletTraces::Update();
}

void
CSpecialFX::Shutdown(void)
{
	C3dMarkers::Shutdown();
	if (gpSmokeTrailTexture) {
		RwTextureDestroy(gpSmokeTrailTexture);
		gpSmokeTrailTexture = nil;
	}
}

void
CSpecialFX::Render(void)
{
	CMotionBlurStreaks::Render();
	CBulletTraces::Render();
	CBrightLights::Render();
	CShinyTexts::Render();
	CMoneyMessages::Render();
	C3dMarkers::Render();
}

void
CSpecialFX::Render2DFXs(void)
{
	if (CSpecialFX::bVideoCam) {
		CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f));
		CFont::SetJustifyOff();
		CFont::SetBackgroundOff();
		CFont::SetCentreSize(SCREEN_SCALE_FROM_RIGHT(20.0f));
		CFont::SetCentreOff();
		CFont::SetPropOn();
		CFont::SetColor(CRGBA(0, 255, 0, 200));
		FONT_LOCALE(FONT_STANDARD);
		sprintf(gString, "%d", CTimer::GetFrameCounter() & 0x3F); // mb % 63
		AsciiToUnicode(gString, gUString);
		CFont::PrintString(SCREEN_WIDTH * 8 / 10, SCREEN_HEIGHT * 8 / 10, gUString);
		for (int32 i = 0; i < SCREEN_HEIGHT; i += 4) {
			RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE);
			RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);
			CSprite2d::Draw2DPolygon(0.0f, i, SCREEN_WIDTH, i, 0.0f, i+1, SCREEN_WIDTH, i+1, CRGBA(0, 100, 0, 100));
			RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
			RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
			CSprite2d::Draw2DPolygon(0.0f, i+2, SCREEN_WIDTH, i+2, 0.0f, i+3, SCREEN_WIDTH, i+3, CRGBA(0, 0, 0, 150));
		}
		int32 tmp = (CTimer::GetTimeInMilliseconds() & 0x7ff) * (SCREEN_HEIGHT + 70.0f) / 2048 - 70.0f; //mb % 2048
		RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
		CSprite2d::Draw2DPolygon(0.0, tmp, SCREEN_WIDTH, tmp, 0.0, tmp + 70.0f, SCREEN_WIDTH, tmp + 70.0f , CRGBA(0, 100, 0, 60));
	}
	if (CSpecialFX::bLiftCam) {
		CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f));
		CFont::SetJustifyOff();
		CFont::SetBackgroundOff();
		CFont::SetCentreSize(SCREEN_SCALE_FROM_RIGHT(20.0f));
		CFont::SetCentreOff();
		CFont::SetPropOn();
		CFont::SetColor(CRGBA(100, 100, 100, 200));
		FONT_LOCALE(FONT_STANDARD);
		CFont::PrintString(SCREEN_WIDTH * 8 / 10, SCREEN_HEIGHT * 8 / 10, gUString);
		for (int32 i = 0; i < SCREEN_HEIGHT; i += 4) {
			RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
			RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
			CSprite2d::Draw2DPolygon(0.0f, i, SCREEN_WIDTH, i, 0.0f, i + 1, SCREEN_WIDTH, i + 1, CRGBA(100, 100, 100, 100));
			RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
			RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
			CSprite2d::Draw2DPolygon(0.0f, i + 2, SCREEN_WIDTH, i + 2, 0.0f, i + 3, SCREEN_WIDTH, i + 3, CRGBA(0, 0, 0, 150));
		}
		int32 tmp = (CTimer::GetTimeInMilliseconds() & 0x7ff) * (SCREEN_HEIGHT + 70.0f) / 2048 - 70.0f; //mb % 2048
		RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
		CSprite2d::Draw2DPolygon(0.0, tmp, SCREEN_WIDTH, tmp, 0.0, tmp + 70.0f, SCREEN_WIDTH, tmp + 70.0f, CRGBA(100, 100, 100, 60));
		for (int32 i = 0; i < 200; i++) {
			int32 posX = CGeneral::GetRandomNumber() % (int32)SCREEN_WIDTH;
			int32 posY = CGeneral::GetRandomNumber() % (int32)SCREEN_HEIGHT;
			CSprite2d::DrawRect(CRect(posX, posY + 2, posX+20, posY), CRGBA(255, 255, 255, 64));
		}
	}
	if (CSpecialFX::bSnapShotActive) {
		if (++CSpecialFX::SnapShotFrames > 20) {
			CSpecialFX::bSnapShotActive = false;
			CTimer::SetTimeScale(1.0f);
		} else {
			CTimer::SetTimeScale(0.0f); //in andro it's 0.00001
			if (CSpecialFX::SnapShotFrames < 10) {
				int32 tmp = (255 - 255 * CSpecialFX::SnapShotFrames / 10) * 0.65f;
				RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
				RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
				CSprite2d::Draw2DPolygon(0.0f, 0.0f, SCREEN_WIDTH, 0.0f, 0.0f, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, CRGBA(tmp, tmp, tmp, tmp));
				RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
				RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
			}
		}
	}
}

CRegisteredMotionBlurStreak CMotionBlurStreaks::aStreaks[NUMMBLURSTREAKS];

void
CRegisteredMotionBlurStreak::Update(void)
{
	int i;
	bool wasUpdated;
	bool lastWasUpdated = false;
	for(i = 2; i > 0; i--){
		m_pos1[i] = m_pos1[i-1];
		m_pos2[i] = m_pos2[i-1];
		m_isValid[i] = m_isValid[i-1];
		wasUpdated = true;
		if(!lastWasUpdated && !m_isValid[i])
			wasUpdated = false;
		lastWasUpdated = wasUpdated;
	}
	m_isValid[0] = false;
	if(!wasUpdated)
		m_id = 0;
}

void
CRegisteredMotionBlurStreak::Render(void)
{
	int i;
	int a1, a2;
	for(i = 0; i < 2; i++)
		if(m_isValid[i] && m_isValid[i+1]){
			a1 = (255/3)*(3-i)/3;
			RwIm3DVertexSetRGBA(&StreakVertices[0], m_red, m_green, m_blue, a1);
			RwIm3DVertexSetRGBA(&StreakVertices[1], m_red, m_green, m_blue, a1);
			a2 = (255/3)*(3-(i+1))/3;
			RwIm3DVertexSetRGBA(&StreakVertices[2], m_red, m_green, m_blue, a2);
			RwIm3DVertexSetRGBA(&StreakVertices[3], m_red, m_green, m_blue, a2);
			RwIm3DVertexSetPos(&StreakVertices[0], m_pos1[i].x, m_pos1[i].y, m_pos1[i].z);
			RwIm3DVertexSetPos(&StreakVertices[1], m_pos2[i].x, m_pos2[i].y, m_pos2[i].z);
			RwIm3DVertexSetPos(&StreakVertices[2], m_pos1[i+1].x, m_pos1[i+1].y, m_pos1[i+1].z);
			RwIm3DVertexSetPos(&StreakVertices[3], m_pos2[i+1].x, m_pos2[i+1].y, m_pos2[i+1].z);
			LittleTest();
			if(RwIm3DTransform(StreakVertices, 4, nil, rwIM3D_VERTEXUV)){
				RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, StreakIndexList, 12);
				RwIm3DEnd();
			}
		}
}

void
CMotionBlurStreaks::Init(void)
{
	int i;
	for(i = 0; i < NUMMBLURSTREAKS; i++)
		aStreaks[i].m_id = 0;
}

void
CMotionBlurStreaks::Update(void)
{
	int i;
	for(i = 0; i < NUMMBLURSTREAKS; i++)
		if(aStreaks[i].m_id)
			aStreaks[i].Update();
}

void
CMotionBlurStreaks::RegisterStreak(uintptr id, uint8 r, uint8 g, uint8 b, CVector p1, CVector p2)
{
	int i;

	for(i = 0; i < NUMMBLURSTREAKS; i++){
		if(aStreaks[i].m_id == id){
			// Found a streak from last frame, update
			aStreaks[i].m_red = r;
			aStreaks[i].m_green = g;
			aStreaks[i].m_blue = b;
			aStreaks[i].m_pos1[0] = p1;
			aStreaks[i].m_pos2[0] = p2;
			aStreaks[i].m_isValid[0] = true;
			return;
		}
	}

	// Find free slot
	for(i = 0; aStreaks[i].m_id != 0 ; i++)
		if(i == NUMMBLURSTREAKS-1)
			return;

	// Create a new streak
	aStreaks[i].m_id = id;
	aStreaks[i].m_red = r;
	aStreaks[i].m_green = g;
	aStreaks[i].m_blue = b;
	aStreaks[i].m_pos1[0] = p1;
	aStreaks[i].m_pos2[0] = p2;
	aStreaks[i].m_isValid[0] = true;
	aStreaks[i].m_isValid[1] = false;
	aStreaks[i].m_isValid[2] = false;
}

void
CMotionBlurStreaks::Render(void)
{
	bool setRenderStates = false;
	int i;
	for(i = 0; i < NUMMBLURSTREAKS; i++)
		if(aStreaks[i].m_id != nil){
			if(!setRenderStates){
				RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
				RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
				RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE);
				RwRenderStateSet(rwRENDERSTATEFOGCOLOR,
					(void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255));
				RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
				RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
				RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)FALSE);
 				setRenderStates = true;
			}
			aStreaks[i].Render();
		}
	if(setRenderStates){
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
		RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
		RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE);
	}
}


CBulletTrace CBulletTraces::aTraces[NUMBULLETTRACES];

void CBulletTraces::Init(void)
{
	for (int i = 0; i < NUMBULLETTRACES; i++)
		aTraces[i].m_bInUse = false;
}

void CBulletTraces::AddTrace(CVector* start, CVector* end, float thicknes, uint32 lifeTime, uint8 visibility)
{
	int32 enabledCount;
	uint32 modifiedLifeTime;
	int32 nextSlot;

	enabledCount = 0;
	for (int i = 0; i < NUMBULLETTRACES; i++)
		if (aTraces[i].m_bInUse)
			enabledCount++;
	if (enabledCount >= 10)
		modifiedLifeTime = lifeTime / 4;
	else if (enabledCount >= 5)
		modifiedLifeTime = lifeTime / 2;
	else
		modifiedLifeTime = lifeTime;

	nextSlot = 0;
	for (int i = 0; nextSlot < NUMBULLETTRACES && aTraces[i].m_bInUse; i++)
		nextSlot++;
	if (nextSlot < 16) {
		aTraces[nextSlot].m_vecStartPos = *start;
		aTraces[nextSlot].m_vecEndPos = *end;
		aTraces[nextSlot].m_bInUse = true;
		aTraces[nextSlot].m_nCreationTime = CTimer::GetTimeInMilliseconds();
		aTraces[nextSlot].m_fVisibility = visibility;
		aTraces[nextSlot].m_fThicknes = thicknes;
		aTraces[nextSlot].m_nLifeTime = modifiedLifeTime;
	}

	float startProjFwd = DotProduct(TheCamera.GetForward(), *start - TheCamera.GetPosition());
	float endProjFwd = DotProduct(TheCamera.GetForward(), *end - TheCamera.GetPosition());
	if (startProjFwd * endProjFwd < 0.0f) { //if one of point behind us and second before us
		float fStartDistFwd = Abs(startProjFwd) / (Abs(startProjFwd) + Abs(endProjFwd));

		float startProjUp = DotProduct(TheCamera.GetUp(), *start - TheCamera.GetPosition());
		float endProjUp = DotProduct(TheCamera.GetUp(), *end - TheCamera.GetPosition());
		float distUp = (endProjUp - startProjUp) * fStartDistFwd + startProjUp;

		float startProjRight = DotProduct(TheCamera.GetRight(), *end - TheCamera.GetPosition());
		float startProjRight = DotProduct(TheCamera.GetRight(), *start - TheCamera.GetPosition());
		float distRight = (startProjRight - startProjRight) * fStartDistFwd + startProjRight;

		float dist = Sqrt(SQR(distUp) + SQR(distRight));
		if (dist < 2.0f) {
			if(distRight < 0.0f)
				DMAudio.PlayFrontEndSound(SOUND_BULLETTRACE_2, 127 * (1.0f - dist * 0.5f));
			else
				DMAudio.PlayFrontEndSound(SOUND_BULLETTRACE_1, 127 * (1.0f - dist * 0.5f));
		}
	}
}

void CBulletTraces::AddTrace(CVector* start, CVector* end, int32 weaponType, class CEntity* shooter)
{
	CPhysical* player;
	float speed;
	int16 camMode;
	
	if (shooter == (CEntity*)FindPlayerPed() || (FindPlayerVehicle() != nil && FindPlayerVehicle() == (CVehicle*)shooter)) {
		camMode = TheCamera.Cams[TheCamera.ActiveCam].Mode;
		if (camMode == CCam::MODE_M16_1STPERSON
			|| camMode == CCam::MODE_CAMERA
			|| camMode == CCam::MODE_SNIPER
			|| camMode == CCam::MODE_M16_1STPERSON_RUNABOUT
			|| camMode == CCam::MODE_ROCKETLAUNCHER
			|| camMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT
			|| camMode == CCam::MODE_SNIPER_RUNABOUT
			|| camMode == CCam::MODE_HELICANNON_1STPERSON) {

			player = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed();
			speed = player->m_vecMoveSpeed.Magnitude();
			if (speed < 0.05f)
				return;
		}
	}

	switch (weaponType) {
	case WEAPONTYPE_PYTHON:
	case WEAPONTYPE_SHOTGUN:
	case WEAPONTYPE_SPAS12_SHOTGUN:
	case WEAPONTYPE_STUBBY_SHOTGUN:
		CBulletTraces::AddTrace(start, end, 0.7f, 1000, 200);
		break;
	case WEAPONTYPE_M4:
	case WEAPONTYPE_RUGER:
	case WEAPONTYPE_SNIPERRIFLE:
	case WEAPONTYPE_LASERSCOPE:
	case WEAPONTYPE_M60:
	case WEAPONTYPE_MINIGUN:
	case WEAPONTYPE_HELICANNON:
		CBulletTraces::AddTrace(start, end, 1.0f, 2000, 220);
		break;
	default:
		CBulletTraces::AddTrace(start, end, 0.4f, 750, 150);
		break;
	}
}

void CBulletTraces::Render(void)
{
	for (int i = 0; i < NUMBULLETTRACES; i++) {
		if (!aTraces[i].m_bInUse)
			continue;
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
		RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
		RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSmokeTrailTexture));

		float timeAlive = CTimer::GetTimeInMilliseconds() - aTraces[i].m_nCreationTime;

		float traceThicknes = aTraces[i].m_fThicknes * timeAlive / aTraces[i].m_nLifeTime;
		CVector horizontalOffset = aTraces[i].m_vecEndPos - aTraces[i].m_vecStartPos;
		horizontalOffset.Normalise();
		horizontalOffset *= traceThicknes;

		//then closer trace to die then it more transparent
		uint8 nAlphaValue = aTraces[i].m_fVisibility * (aTraces[i].m_nLifeTime - timeAlive) / aTraces[i].m_nLifeTime;

		CVector start = aTraces[i].m_vecStartPos;
		CVector end = aTraces[i].m_vecEndPos;
		float startProj = DotProduct(start - TheCamera.GetPosition(), TheCamera.GetForward()) - 0.7f;
		float endProj = DotProduct(end - TheCamera.GetPosition(), TheCamera.GetForward()) - 0.7f;
		if (startProj < 0.0f && endProj < 0.0f) //we dont need render trace behind us
			continue;

		if (startProj < 0.0f) { //if strat behind us move it closer
			float absStartProj = Abs(startProj);
			float absEndProj = Abs(endProj);
			start = (absEndProj * start + absStartProj * end) / (absStartProj + absEndProj);
		} else if (endProj < 0.0f) {
			float absStartProj = Abs(startProj);
			float absEndProj = Abs(endProj);
			end = (absEndProj * start + absStartProj * end) / (absStartProj + absEndProj);
		}

		//we divide trace at three parts
		CVector start2 = (7.0f * start + end) / 8;
		CVector end2 = (7.0f * end + start) / 8;

		RwIm3DVertexSetV(&TraceVertices[5], 10.0f);
		RwIm3DVertexSetV(&TraceVertices[6], 10.0f);
		RwIm3DVertexSetV(&TraceVertices[7], 10.0f);
		RwIm3DVertexSetV(&TraceVertices[8], 10.0f);
		RwIm3DVertexSetV(&TraceVertices[9], 10.0f);

		RwIm3DVertexSetRGBA(&TraceVertices[0], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[1], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[2], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[3], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[4], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[5], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[6], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[7], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[8], 255, 255, 255, nAlphaValue);
		RwIm3DVertexSetRGBA(&TraceVertices[9], 255, 255, 255, nAlphaValue);
		//two points in center
		RwIm3DVertexSetPos(&TraceVertices[0], start2.x, start2.y, start2.z);
		RwIm3DVertexSetPos(&TraceVertices[5], end2.x, end2.y, end2.z);
		//vetrical planes
		RwIm3DVertexSetPos(&TraceVertices[1], start2.x, start2.y, start2.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[3], start2.x, start2.y, start2.z - traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[6], end2.x, end2.y, end2.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[8], end2.x, end2.y, end2.z - traceThicknes);
		//horizontal planes
		RwIm3DVertexSetPos(&TraceVertices[2], start2.x + horizontalOffset.y, start2.y - horizontalOffset.x, start2.z);
		RwIm3DVertexSetPos(&TraceVertices[7], end2.x + horizontalOffset.y, end2.y - horizontalOffset.x, end2.z);
#ifdef FIX_BUGS //this point calcilated wrong for some reason
		RwIm3DVertexSetPos(&TraceVertices[4], start2.x - horizontalOffset.y, start2.y + horizontalOffset.x, start2.z);
		RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y + horizontalOffset.x, end2.z);
#else
		RwIm3DVertexSetPos(&TraceVertices[4], start2.x - horizontalOffset.y, start2.y - horizontalOffset.y, start2.z);
		RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y - horizontalOffset.y, end2.z);
#endif

		if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, 1)) {
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList));
			RwIm3DEnd();
		}

		RwIm3DVertexSetV(&TraceVertices[5], 2.0f);
		RwIm3DVertexSetV(&TraceVertices[6], 2.0f);
		RwIm3DVertexSetV(&TraceVertices[7], 2.0f);
		RwIm3DVertexSetV(&TraceVertices[8], 2.0f);
		RwIm3DVertexSetV(&TraceVertices[9], 2.0f);
		RwIm3DVertexSetRGBA(&TraceVertices[0], 255, 255, 255, 0);
		RwIm3DVertexSetRGBA(&TraceVertices[1], 255, 255, 255, 0);
		RwIm3DVertexSetRGBA(&TraceVertices[2], 255, 255, 255, 0);
		RwIm3DVertexSetRGBA(&TraceVertices[3], 255, 255, 255, 0);
		RwIm3DVertexSetRGBA(&TraceVertices[4], 255, 255, 255, 0);

		RwIm3DVertexSetPos(&TraceVertices[0], start.x, start.y, start.z);
		RwIm3DVertexSetPos(&TraceVertices[1], start.x, start.y, start.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[3], start.x, start.y, start.z - traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[2], start.x + horizontalOffset.y, start.y - horizontalOffset.x, start.z);

		RwIm3DVertexSetPos(&TraceVertices[5], start2.x, start2.y, start2.z);
		RwIm3DVertexSetPos(&TraceVertices[6], start2.x, start2.y, start2.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[8], start2.x, start2.y, start2.z - traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[7], start2.x + horizontalOffset.y, start2.y - horizontalOffset.x, start2.z);
#ifdef FIX_BUGS
		RwIm3DVertexSetPos(&TraceVertices[4], start.x - horizontalOffset.y, start.y + horizontalOffset.x, start.z);
		RwIm3DVertexSetPos(&TraceVertices[9], start2.x - horizontalOffset.y, start2.y + horizontalOffset.x, start2.z);
#else
		RwIm3DVertexSetPos(&TraceVertices[4], start.x - horizontalOffset.y, start.y - horizontalOffset.y, start.z);
		RwIm3DVertexSetPos(&TraceVertices[9], start2.x - horizontalOffset.y, start2.y - horizontalOffset.y, start2.z);
#endif

		if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, rwIM3D_VERTEXUV)) {
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList));
			RwIm3DEnd();
		}

		RwIm3DVertexSetPos(&TraceVertices[1], end.x, end.y, end.z);
		RwIm3DVertexSetPos(&TraceVertices[2], end.x, end.y, end.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[4], end.x, end.y, end.z - traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[3], end.x + horizontalOffset.y, end.y - horizontalOffset.x, end.z);

		RwIm3DVertexSetPos(&TraceVertices[5], end2.x, end2.y, end2.z);
		RwIm3DVertexSetPos(&TraceVertices[6], end2.x, end2.y, end2.z + traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[8], end2.x, end2.y, end2.z - traceThicknes);
		RwIm3DVertexSetPos(&TraceVertices[7], end2.x + horizontalOffset.y, end2.y - horizontalOffset.x, end2.z);
#ifdef FIX_BUGS
		RwIm3DVertexSetPos(&TraceVertices[5], end.x - horizontalOffset.y, end.y + horizontalOffset.x, end.z);
		RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y + horizontalOffset.x, end2.z);
#else
		RwIm3DVertexSetPos(&TraceVertices[5], end.x - horizontalOffset.y, end.y - horizontalOffset.y, end.z);
		RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y - horizontalOffset.y, end2.z);
#endif

		if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, rwIM3D_VERTEXUV)) {
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList));
			RwIm3DEnd();
		}
	}
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
}

void CBulletTraces::Update(void)
{
	for (int i = 0; i < NUMBULLETTRACES; i++) {
		if (aTraces[i].m_bInUse)
			aTraces[i].Update();
	}
}

void CBulletTrace::Update(void)
{
	if (CTimer::GetTimeInMilliseconds() - m_nCreationTime >= m_nLifeTime)
		m_bInUse = false;
}

RpAtomic *
MarkerAtomicCB(RpAtomic *atomic, void *data)
{
	*(RpAtomic**)data = atomic;
	return atomic;
}

// --MIAMI: C3dMarker and C3dMarkers done

bool
C3dMarker::AddMarker(uint32 identifier, uint16 type, float fSize, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate)
{
	m_nIdentifier = identifier;

	m_Matrix.SetUnity();

	RpAtomic *origAtomic;
	origAtomic = nil;
	RpClumpForAllAtomics(C3dMarkers::m_pRpClumpArray[type], MarkerAtomicCB, &origAtomic);

	RpAtomic *atomic = RpAtomicClone(origAtomic);
	RwFrame *frame = RwFrameCreate();
	RpAtomicSetFrame(atomic, frame);
	CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
	
	RpGeometry *geometry = RpAtomicGetGeometry(atomic);
	RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR);

	m_pAtomic = atomic;
	m_Matrix.Attach(RwFrameGetMatrix(RpAtomicGetFrame(m_pAtomic)));
	m_pMaterial = RpGeometryGetMaterial(geometry, 0);
	m_fSize = fSize;
	m_fStdSize = m_fSize;
	m_Color.red = r;
	m_Color.green = g;
	m_Color.blue = b;
	m_Color.alpha = a;
	m_nPulsePeriod = pulsePeriod;
	m_fPulseFraction = pulseFraction;
	m_nRotateRate = rotateRate;
	m_nStartTime = CTimer::GetTimeInMilliseconds();
	m_nType = type;
	return m_pAtomic != nil;
}

void
C3dMarker::DeleteMarkerObject()
{
	RwFrame *frame;

	m_nIdentifier = 0;
	m_nStartTime = 0;
	m_bIsUsed = false;
	m_bFindZOnNextPlacement = false;
	m_nType = MARKERTYPE_INVALID;

	frame = RpAtomicGetFrame(m_pAtomic);
	RpAtomicDestroy(m_pAtomic);
	RwFrameDestroy(frame);
	m_pAtomic = nil;
}

void
C3dMarker::Render()
{
	if (m_pAtomic == nil) return;

	RpMaterialSetColor(m_pMaterial, &m_Color);

	m_Matrix.UpdateRW();

	CMatrix matrix;
	matrix.Attach(m_Matrix.m_attachment);
	matrix.Scale(m_fSize);
	matrix.UpdateRW();

	RwFrameUpdateObjects(RpAtomicGetFrame(m_pAtomic));
	SetBrightMarkerColours(m_fBrightness);
	if (m_nType != MARKERTYPE_ARROW)
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
	RpAtomicRender(m_pAtomic);
	if (m_nType != MARKERTYPE_ARROW)
		RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
	ReSetAmbientAndDirectionalColours();
}

C3dMarker C3dMarkers::m_aMarkerArray[NUM3DMARKERS];
int32 C3dMarkers::NumActiveMarkers;
RpClump* C3dMarkers::m_pRpClumpArray[NUMMARKERTYPES];

void
C3dMarkers::Init()
{
	for (int i = 0; i < NUM3DMARKERS; i++) {
		m_aMarkerArray[i].m_pAtomic = nil;
		m_aMarkerArray[i].m_nType = MARKERTYPE_INVALID;
		m_aMarkerArray[i].m_bIsUsed = false;
		m_aMarkerArray[i].m_bFindZOnNextPlacement = false;
		m_aMarkerArray[i].m_nIdentifier = 0;
		m_aMarkerArray[i].m_Color.red = 255;
		m_aMarkerArray[i].m_Color.green = 255;
		m_aMarkerArray[i].m_Color.blue = 255;
		m_aMarkerArray[i].m_Color.alpha = 255;
		m_aMarkerArray[i].m_nPulsePeriod = 1024;
		m_aMarkerArray[i].m_nRotateRate = 5;
		m_aMarkerArray[i].m_nStartTime = 0;
		m_aMarkerArray[i].m_fPulseFraction = 0.25f;
		m_aMarkerArray[i].m_fStdSize = 1.0f;
		m_aMarkerArray[i].m_fSize = 1.0f;
		m_aMarkerArray[i].m_fBrightness = 1.0f;
		m_aMarkerArray[i].m_fCameraRange = 0.0f;
	}
	NumActiveMarkers = 0;
	int txdSlot = CTxdStore::FindTxdSlot("particle");
	CTxdStore::PushCurrentTxd();
	CTxdStore::SetCurrentTxd(txdSlot);
	CFileMgr::ChangeDir("\\");
	m_pRpClumpArray[MARKERTYPE_ARROW] = CFileLoader::LoadAtomicFile2Return("models/generic/arrow.dff");
	m_pRpClumpArray[MARKERTYPE_CYLINDER] = CFileLoader::LoadAtomicFile2Return("models/generic/zonecylb.dff");
	CTxdStore::PopCurrentTxd();
}

void
C3dMarkers::Shutdown()
{
	for (int i = 0; i < NUM3DMARKERS; i++) {
		if (m_aMarkerArray[i].m_pAtomic != nil)
			m_aMarkerArray[i].DeleteMarkerObject();
	}

	for (int i = 0; i < NUMMARKERTYPES; i++) {
		if (m_pRpClumpArray[i] != nil)
			RpClumpDestroy(m_pRpClumpArray[i]);
	}
}

void
C3dMarkers::Render()
{
	NumActiveMarkers = 0;
	ActivateDirectional();
	for (int i = 0; i < NUM3DMARKERS; i++) {
		if (m_aMarkerArray[i].m_bIsUsed) {
			if (m_aMarkerArray[i].m_fCameraRange < 150.0f) {
				m_aMarkerArray[i].Render();
				if (m_aMarkerArray[i].m_nType == MARKERTYPE_ARROW) {
					CCoronas::RegisterCorona((uintptr)&m_aMarkerArray[i],
						SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, 192,
						m_aMarkerArray[i].m_Matrix.GetPosition(), 1.2f * m_aMarkerArray[i].m_fSize, 50.0f * TheCamera.LODDistMultiplier,
						CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f, false);
				}
			}
			NumActiveMarkers++;
			m_aMarkerArray[i].m_bIsUsed = false;
		} else if (m_aMarkerArray[i].m_pAtomic != nil) {
			m_aMarkerArray[i].DeleteMarkerObject();
		}
	}
}

C3dMarker *
C3dMarkers::PlaceMarker(uint32 identifier, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate)
{
	C3dMarker *pMarker;
	CVector2D playerPos = FindPlayerCentreOfWorld(0);
	pMarker = nil;
	float dist = ((CVector2D)pos - playerPos).Magnitude();

	if (type != MARKERTYPE_ARROW && type != MARKERTYPE_CYLINDER) return nil;

	for (int i = 0; i < NUM3DMARKERS; i++) {
		if (!m_aMarkerArray[i].m_bIsUsed && m_aMarkerArray[i].m_nIdentifier == identifier) {
			pMarker = &m_aMarkerArray[i];
			break;
		}
	}

	if (pMarker == nil) {
		for (int i = 0; i < NUM3DMARKERS; i++) {
			if (m_aMarkerArray[i].m_nType == MARKERTYPE_INVALID) {
				pMarker = &m_aMarkerArray[i];
				break;
			}
		}
	}

	if (pMarker == nil && type == MARKERTYPE_ARROW) {
		for (int i = 0; i < NUM3DMARKERS; i++) {
			if (dist < m_aMarkerArray[i].m_fCameraRange && m_aMarkerArray[i].m_nType == MARKERTYPE_ARROW && (pMarker == nil || m_aMarkerArray[i].m_fCameraRange > pMarker->m_fCameraRange)) {
				pMarker = &m_aMarkerArray[i];
				break;
			}
		}

		if (pMarker != nil)
			pMarker->m_nType = MARKERTYPE_INVALID;
	}

	if (pMarker == nil) return pMarker;

	pMarker->m_fCameraRange = dist;
	if (pMarker->m_nIdentifier == identifier && pMarker->m_nType == type) {
		if (type == MARKERTYPE_ARROW) {
			if (dist < 25.0f) {
				if (dist > 5.0f)
					pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f;
				else
					pMarker->m_fStdSize = size - 0.3f * size;
			} else {
				pMarker->m_fStdSize = size;
			}
		} else {
			if (dist < size + 12.0f) {
				if (dist > size + 1.0f)
					pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a;
				else
					pMarker->m_Color.alpha = (float)a * 0.3f;
			} else {
				pMarker->m_Color.alpha = a;
			}
		}
		float someSin = Sin(TWOPI * (float)((pMarker->m_nPulsePeriod - 1) & (CTimer::GetTimeInMilliseconds() - pMarker->m_nStartTime)) / (float)pMarker->m_nPulsePeriod);
		pMarker->m_fSize = pMarker->m_fStdSize - pulseFraction * pMarker->m_fStdSize * someSin;

		if (type == MARKERTYPE_ARROW)
			pos.z += 0.25f * pMarker->m_fStdSize * someSin;
		if (pMarker->m_nRotateRate != 0) {
			RwV3d pos = pMarker->m_Matrix.m_matrix.pos;
			pMarker->m_Matrix.RotateZ(DEGTORAD(pMarker->m_nRotateRate * CTimer::GetTimeStep()));
			pMarker->m_Matrix.GetPosition() = pos;
		}
		if (type == MARKERTYPE_ARROW)
			pMarker->m_Matrix.GetPosition() = pos;

		if (pMarker->m_bFindZOnNextPlacement) {
			if ((playerPos - pos).MagnitudeSqr() < sq(100.f) && CColStore::HasCollisionLoaded(CVector2D(pos))) {
				float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 1.0f, nil);
				if (z != 0.0f)
					pMarker->m_Matrix.GetPosition().z = z - 0.05f * size;
				pMarker->m_bFindZOnNextPlacement = false;
			}
		}
		pMarker->m_bIsUsed = true;
		return pMarker;
	}

	if (pMarker->m_nIdentifier != 0)
		pMarker->DeleteMarkerObject();

	pMarker->AddMarker(identifier, type, size, r, g, b, a, pulsePeriod, pulseFraction, rotateRate);
	if (type == MARKERTYPE_CYLINDER) {
		if ((playerPos - pos).MagnitudeSqr() < sq(100.f) && CColStore::HasCollisionLoaded(CVector2D(pos))) {
			float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 1.0f, nil);
			if (z != 0.0f)
				pos.z = z - 0.05f * size;
			pMarker->m_bFindZOnNextPlacement = false;
		} else {
			pMarker->m_bFindZOnNextPlacement = true;
		}
	}
	pMarker->m_Matrix.SetTranslate(pos.x, pos.y, pos.z);
	pMarker->m_Matrix.UpdateRW();
	if (type == MARKERTYPE_ARROW) {
		if (dist < 25.0f) {
			if (dist > 5.0f)
				pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f;
			else
				pMarker->m_fStdSize = size - 0.3f * size;
		} else {
			pMarker->m_fStdSize = size;
		}
	} else {
		if (dist < size + 12.0f) {
			if (dist > size + 1.0f)
				pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a;
			else
				pMarker->m_Color.alpha = (float)a * 0.3f;
		} else {
			pMarker->m_Color.alpha = a;
		}
	}
	pMarker->m_bIsUsed = true;
	return pMarker;
}

void
C3dMarkers::PlaceMarkerSet(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate)
{
	PlaceMarker(id, type, pos, size, r, g, b, a, pulsePeriod, pulseFraction, 1);
	PlaceMarker(id, type, pos, size * 0.93f, r, g, b, a, pulsePeriod, pulseFraction, 2);
	PlaceMarker(id, type, pos, size * 0.86f, r, g, b, a, pulsePeriod, pulseFraction, -1);
}


void
C3dMarkers::Update()
{
}


#define BRIGHTLIGHTS_MAX_DIST (60.0f)	// invisible beyond this
#define BRIGHTLIGHTS_FADE_DIST (45.0f)	// strongest between these two
#define CARLIGHTS_MAX_DIST (30.0f)
#define CARLIGHTS_FADE_DIST (15.0f)	// 31 for close lights

int CBrightLights::NumBrightLights;
CBrightLight CBrightLights::aBrightLights[NUMBRIGHTLIGHTS];

void
CBrightLights::Init(void)
{
	NumBrightLights = 0;
}

void
CBrightLights::RegisterOne(CVector pos, CVector up, CVector side, CVector front,
	uint8 type, uint8 red, uint8 green, uint8 blue)
{
	if(NumBrightLights >= NUMBRIGHTLIGHTS)
		return;

	aBrightLights[NumBrightLights].m_camDist = (pos - TheCamera.GetPosition()).Magnitude();
	if(aBrightLights[NumBrightLights].m_camDist > BRIGHTLIGHTS_MAX_DIST)
		return;

	aBrightLights[NumBrightLights].m_pos = pos;
	aBrightLights[NumBrightLights].m_up = up;
	aBrightLights[NumBrightLights].m_side = side;
	aBrightLights[NumBrightLights].m_front = front;
	aBrightLights[NumBrightLights].m_type = type;
	aBrightLights[NumBrightLights].m_red = red;
	aBrightLights[NumBrightLights].m_green = green;
	aBrightLights[NumBrightLights].m_blue = blue;

	NumBrightLights++;
}

static float TrafficLightsSide[6] = { -0.09f, 0.09f, 0.162f, 0.09f, -0.09f, -0.162f };
static float TrafficLightsUp[6] = { 0.162f, 0.162f, 0.0f, -0.162f, -0.162f, 0.0f };
static float LongCarHeadLightsSide[8] = { -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f };
static float LongCarHeadLightsFront[8] = { 0.1f, 0.1f, -0.1f, -0.1f, 0.1f, 0.1f, -0.1f, -0.1f };
static float LongCarHeadLightsUp[8] = { 0.1f, 0.1f, 0.1f, 0.1f, -0.1f, -0.1f, -0.1f, -0.1f };
static float SmallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f };
static float SmallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f };
static float SmallCarHeadLightsUp[8] = { 0.08f, 0.08f, 0.08f, 0.08f, -0.08f, -0.08f, -0.08f, -0.08f };
static float BigCarHeadLightsSide[8] = { -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f };
static float BigCarHeadLightsFront[8] = { 0.15f, 0.15f, -0.15f, -0.15f, 0.15f, 0.15f, -0.15f, -0.15f };
static float BigCarHeadLightsUp[8] = { 0.15f, 0.15f, 0.15f, 0.15f, -0.15f, -0.15f, -0.15f, -0.15f };
static float TallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f };
static float TallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f };
static float TallCarHeadLightsUp[8] = { 0.2f, 0.2f, 0.2f, 0.2f, -0.2f, -0.2f, -0.2f, -0.2f };
static float SirenLightsSide[6] = { -0.04f, 0.04f, 0.06f, 0.04f, -0.04f, -0.06f };
static float SirenLightsUp[6] = { 0.06f, 0.06f, 0.0f, -0.06f, -0.06f, 0.0f };
static RwImVertexIndex TrafficLightIndices[4*3] = { 0, 1, 5,  1, 2, 3,  1, 3, 4,  1, 4, 5 };
static RwImVertexIndex CubeIndices[12*3] = {
	0, 2, 1,  1, 2, 3,  3, 5, 1,  3, 7, 5,
	2, 7, 3,  2, 6, 7,  4, 0, 1,  4, 1, 5,
	6, 0, 4,  6, 2, 0,  6, 5, 7,  6, 4, 5
};

void
CBrightLights::Render(void)
{
	int i, j;
	CVector pos;

	if(NumBrightLights == 0)
		return;

	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);

	TempBufferVerticesStored = 0;
	TempBufferIndicesStored = 0;

	for(i = 0; i < NumBrightLights; i++){
		if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-40 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-40)
			RenderOutGeometryBuffer();

		int r, g, b, a;
		float flicker = (CGeneral::GetRandomNumber()&0xFF) * 0.2f;
		switch(aBrightLights[i].m_type){
		case BRIGHTLIGHT_TRAFFIC_GREEN:
			r = flicker; g = 255; b = flicker;
			break;
		case BRIGHTLIGHT_TRAFFIC_YELLOW:
			r = 255; g = 128; b = flicker;
			break;
		case BRIGHTLIGHT_TRAFFIC_RED:
			r = 255; g = flicker; b = flicker;
			break;

		case BRIGHTLIGHT_FRONT_LONG:
		case BRIGHTLIGHT_FRONT_SMALL:
		case BRIGHTLIGHT_FRONT_BIG:
		case BRIGHTLIGHT_FRONT_TALL:
			r = 255; g = 255; b = 255;
			break;

		case BRIGHTLIGHT_REAR_LONG:
		case BRIGHTLIGHT_REAR_SMALL:
		case BRIGHTLIGHT_REAR_BIG:
		case BRIGHTLIGHT_REAR_TALL:
			r = 255; g = flicker; b = flicker;
			break;

		case BRIGHTLIGHT_SIREN:
			r = aBrightLights[i].m_red;
			g = aBrightLights[i].m_green;
			b = aBrightLights[i].m_blue;
			break;
		default:
#ifdef FIX_BUGS  //just to make sure that color never will be undefined
			return;
#else
			break;
#endif
		}

		if(aBrightLights[i].m_camDist < BRIGHTLIGHTS_FADE_DIST)
			a = 255;
		else
			a = 255*(1.0f - (aBrightLights[i].m_camDist-BRIGHTLIGHTS_FADE_DIST)/(BRIGHTLIGHTS_MAX_DIST-BRIGHTLIGHTS_FADE_DIST));
		// fade car lights down to 31 as they come near
		if(aBrightLights[i].m_type >= BRIGHTLIGHT_FRONT_LONG && aBrightLights[i].m_type <= BRIGHTLIGHT_REAR_TALL){
			if(aBrightLights[i].m_camDist < CARLIGHTS_FADE_DIST)
				a = 31;
			else if(aBrightLights[i].m_camDist < CARLIGHTS_MAX_DIST)
				a = 31 + (255-31)*((aBrightLights[i].m_camDist-CARLIGHTS_FADE_DIST)/(CARLIGHTS_MAX_DIST-CARLIGHTS_FADE_DIST));
		}

		switch(aBrightLights[i].m_type){
		case BRIGHTLIGHT_TRAFFIC_GREEN:
		case BRIGHTLIGHT_TRAFFIC_YELLOW:
		case BRIGHTLIGHT_TRAFFIC_RED:
			for(j = 0; j < 6; j++){
				pos = TrafficLightsSide[j]*aBrightLights[i].m_side +
					TrafficLightsUp[j]*aBrightLights[i].m_up +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z);
			}
			for(j = 0; j < 4*3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 6;
			TempBufferIndicesStored += 4*3;
			break;

		case BRIGHTLIGHT_FRONT_LONG:
		case BRIGHTLIGHT_REAR_LONG:
			for(j = 0; j < 8; j++){
				pos = LongCarHeadLightsSide[j]*aBrightLights[i].m_side +
					LongCarHeadLightsUp[j]*aBrightLights[i].m_up +
					LongCarHeadLightsFront[j]*aBrightLights[i].m_front +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z);
			}
			for(j = 0; j < 12*3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 8;
			TempBufferIndicesStored += 12*3;
			break;

		case BRIGHTLIGHT_FRONT_SMALL:
		case BRIGHTLIGHT_REAR_SMALL:
			for(j = 0; j < 8; j++){
				pos = SmallCarHeadLightsSide[j]*aBrightLights[i].m_side +
					SmallCarHeadLightsUp[j]*aBrightLights[i].m_up +
					SmallCarHeadLightsFront[j]*aBrightLights[i].m_front +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z);
			}
			for(j = 0; j < 12*3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 8;
			TempBufferIndicesStored += 12*3;
			break;

		case BRIGHTLIGHT_FRONT_BIG:
		case BRIGHTLIGHT_REAR_BIG:
			for (j = 0; j < 8; j++) {
				pos = BigCarHeadLightsSide[j] * aBrightLights[i].m_side +
					BigCarHeadLightsUp[j] * aBrightLights[i].m_up +
					BigCarHeadLightsFront[j] * aBrightLights[i].m_front +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + j], pos.x, pos.y, pos.z);
			}
			for (j = 0; j < 12 * 3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored + j] = CubeIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 8;
			TempBufferIndicesStored += 12 * 3;
			break;

		case BRIGHTLIGHT_FRONT_TALL:
		case BRIGHTLIGHT_REAR_TALL:
			for(j = 0; j < 8; j++){
				pos = TallCarHeadLightsSide[j]*aBrightLights[i].m_side +
					TallCarHeadLightsUp[j]*aBrightLights[i].m_up +
					TallCarHeadLightsFront[j]*aBrightLights[i].m_front +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z);
			}
			for(j = 0; j < 12*3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 8;
			TempBufferIndicesStored += 12*3;
			break;

		case BRIGHTLIGHT_SIREN:
			for(j = 0; j < 6; j++){
				pos = SirenLightsSide[j]*TheCamera.GetRight() +
					SirenLightsUp[j]* TheCamera.GetUp() +
					aBrightLights[i].m_pos;
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z);
			}
			for(j = 0; j < 4*3; j++)
				TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored;
			TempBufferVerticesStored += 6;
			TempBufferIndicesStored += 4*3;
			break;

		}
	}

	RenderOutGeometryBuffer();
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
	NumBrightLights = 0;
}

void
CBrightLights::RenderOutGeometryBuffer(void)
{
	if(TempBufferIndicesStored != 0){
		LittleTest();
		if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
			RwIm3DEnd();
		}
		TempBufferVerticesStored = 0;
		TempBufferIndicesStored = 0;
	}
}

int CShinyTexts::NumShinyTexts;
CShinyText CShinyTexts::aShinyTexts[NUMSHINYTEXTS];

void
CShinyTexts::Init(void)
{
	NumShinyTexts = 0;
}

void
CShinyTexts::RegisterOne(CVector p0, CVector p1, CVector p2, CVector p3,
	float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3,
	uint8 type, uint8 red, uint8 green, uint8 blue, float maxDist)
{
	if(NumShinyTexts >= NUMSHINYTEXTS)
		return;

	aShinyTexts[NumShinyTexts].m_camDist = (p0 - TheCamera.GetPosition()).Magnitude();
	if(aShinyTexts[NumShinyTexts].m_camDist > maxDist)
		return;
	aShinyTexts[NumShinyTexts].m_verts[0] = p0;
	aShinyTexts[NumShinyTexts].m_verts[1] = p1;
	aShinyTexts[NumShinyTexts].m_verts[2] = p2;
	aShinyTexts[NumShinyTexts].m_verts[3] = p3;
	aShinyTexts[NumShinyTexts].m_texCoords[0].x = u0;
	aShinyTexts[NumShinyTexts].m_texCoords[0].y = v0;
	aShinyTexts[NumShinyTexts].m_texCoords[1].x = u1;
	aShinyTexts[NumShinyTexts].m_texCoords[1].y = v1;
	aShinyTexts[NumShinyTexts].m_texCoords[2].x = u2;
	aShinyTexts[NumShinyTexts].m_texCoords[2].y = v2;
	aShinyTexts[NumShinyTexts].m_texCoords[3].x = u3;
	aShinyTexts[NumShinyTexts].m_texCoords[3].y = v3;
	aShinyTexts[NumShinyTexts].m_type = type;
	aShinyTexts[NumShinyTexts].m_red = red;
	aShinyTexts[NumShinyTexts].m_green = green;
	aShinyTexts[NumShinyTexts].m_blue = blue;
	// Fade out at half the max dist
	float halfDist = maxDist*0.5f;
	if(aShinyTexts[NumShinyTexts].m_camDist > halfDist){
		float f = 1.0f - (aShinyTexts[NumShinyTexts].m_camDist - halfDist)/halfDist;
		aShinyTexts[NumShinyTexts].m_red *= f;
		aShinyTexts[NumShinyTexts].m_green *= f;
		aShinyTexts[NumShinyTexts].m_blue *= f;
	}

	NumShinyTexts++;
}

void
CShinyTexts::Render(void)
{
	int i, ix, v;
	RwTexture *lastTex = nil;

	if(NumShinyTexts == 0)
		return;

	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);

	TempBufferVerticesStored = 0;
	TempBufferIndicesStored = 0;

	for(i = 0; i < NumShinyTexts; i++){
		if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-64 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-62)
			RenderOutGeometryBuffer();

		uint8 r = aShinyTexts[i].m_red;
		uint8 g = aShinyTexts[i].m_green;
		uint8 b = aShinyTexts[i].m_blue;

		switch(aShinyTexts[i].m_type){
		case SHINYTEXT_WALK:
			if(lastTex != gpWalkDontTex){
				RenderOutGeometryBuffer();
				RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpWalkDontTex));
				lastTex = gpWalkDontTex;
			}
	quad:
			v = TempBufferVerticesStored;
			RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], r, g, b, 255);
			RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_verts[0].x, aShinyTexts[i].m_verts[0].y, aShinyTexts[i].m_verts[0].z);
			RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].x);
			RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].y);
			RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], r, g, b, 255);
			RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_verts[1].x, aShinyTexts[i].m_verts[1].y, aShinyTexts[i].m_verts[1].z);
			RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].x);
			RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].y);
			RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], r, g, b, 255);
			RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_verts[2].x, aShinyTexts[i].m_verts[2].y, aShinyTexts[i].m_verts[2].z);
			RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].x);
			RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].y);
			RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], r, g, b, 255);
			RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_verts[3].x, aShinyTexts[i].m_verts[3].y, aShinyTexts[i].m_verts[3].z);
			RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].x);
			RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].y);
			ix = TempBufferIndicesStored;
			TempBufferRenderIndexList[ix+0] = 0 + TempBufferVerticesStored;
			TempBufferRenderIndexList[ix+1] = 1 + TempBufferVerticesStored;
			TempBufferRenderIndexList[ix+2] = 2 + TempBufferVerticesStored;
			TempBufferRenderIndexList[ix+3] = 2 + TempBufferVerticesStored;
			TempBufferRenderIndexList[ix+4] = 1 + TempBufferVerticesStored;
			TempBufferRenderIndexList[ix+5] = 3 + TempBufferVerticesStored;
			TempBufferVerticesStored += 4;
			TempBufferIndicesStored += 6;
			break;

		case SHINYTEXT_FLAT:
			if(lastTex != nil){
				RenderOutGeometryBuffer();
				RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
				lastTex = nil;
			}
			goto quad;
		}
	}

	RenderOutGeometryBuffer();
	NumShinyTexts = 0;

	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
}

void
CShinyTexts::RenderOutGeometryBuffer(void)
{
	if(TempBufferIndicesStored != 0){
		LittleTest();
		if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
			RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
			RwIm3DEnd();
		}
		TempBufferVerticesStored = 0;
		TempBufferIndicesStored = 0;
	}
}



#define MONEY_MESSAGE_LIFETIME_MS 2000

CMoneyMessage CMoneyMessages::aMoneyMessages[NUMMONEYMESSAGES];

void
CMoneyMessage::Render()
{
	const float MAX_SCALE = 4.0f;
	uint32 nLifeTime = CTimer::GetTimeInMilliseconds() - m_nTimeRegistered;
	if (nLifeTime >= MONEY_MESSAGE_LIFETIME_MS) {
		m_nTimeRegistered = 0;
	} else {
		float fLifeTime = (float)nLifeTime / MONEY_MESSAGE_LIFETIME_MS;
		RwV3d vecOut;
		float fDistX, fDistY;
		if (CSprite::CalcScreenCoors(m_vecPosition + CVector(0.0f, 0.0f, fLifeTime), &vecOut, &fDistX, &fDistY, true)) {
			fDistX *= (0.7 * fLifeTime + 2.0) * m_fSize;
			fDistY *= (0.7 * fLifeTime + 2.0) * m_fSize;
			CFont::SetPropOn();
			CFont::SetBackgroundOff();
			float fScaleY = Min(fDistY / 100.0f, MAX_SCALE);
			float fScaleX = Min(fDistX / 100.0f, MAX_SCALE);
			CFont::SetScale(fScaleX, fScaleY); // maybe use SCREEN_SCALE_X and SCREEN_SCALE_Y here?
			CFont::SetCentreOn();
			CFont::SetCentreSize(SCREEN_WIDTH);
			CFont::SetJustifyOff();
			CFont::SetColor(CRGBA(m_Colour.r, m_Colour.g, m_Colour.b, (255.0f - 255.0f * fLifeTime) * m_fOpacity));
			CFont::SetBackGroundOnlyTextOff();
			FONT_LOCALE(FONT_STANDARD);
			CFont::PrintString(vecOut.x, vecOut.y, m_aText);
		}
	}
}

void
CMoneyMessages::Init()
{
	for (int32 i = 0; i < NUMMONEYMESSAGES; i++)
		aMoneyMessages[i].m_nTimeRegistered = 0;
}

void
CMoneyMessages::Render()
{
	for (int32 i = 0; i < NUMMONEYMESSAGES; i++) {
		if (aMoneyMessages[i].m_nTimeRegistered != 0)
			aMoneyMessages[i].Render();
	}
}

void
CMoneyMessages::RegisterOne(CVector vecPos, const char *pText, uint8 bRed, uint8 bGreen, uint8 bBlue, float fSize, float fOpacity)
{
	uint32 nIndex = 0;
	while (aMoneyMessages[nIndex].m_nTimeRegistered != 0) {
		if (++nIndex >= NUMMONEYMESSAGES) return;
	}

	// Add data of this money message to the array
	AsciiToUnicode(pText, aMoneyMessages[nIndex].m_aText);

	aMoneyMessages[nIndex].m_nTimeRegistered = CTimer::GetTimeInMilliseconds();
	aMoneyMessages[nIndex].m_vecPosition = vecPos;
	aMoneyMessages[nIndex].m_Colour.red = bRed;
	aMoneyMessages[nIndex].m_Colour.green = bGreen;
	aMoneyMessages[nIndex].m_Colour.blue = bBlue;
	aMoneyMessages[nIndex].m_fSize = fSize;
	aMoneyMessages[nIndex].m_fOpacity = fOpacity;
}

CRGBA FoamColour(255, 255, 255, 255);
unsigned int CSpecialParticleStuff::BoatFromStart;

void
CSpecialParticleStuff::CreateFoamAroundObject(CMatrix* pMatrix, float innerFw, float innerRg, float innerUp, int32 particles)
{
	float outerFw = innerFw + 5.0f;
	float outerRg = innerRg + 5.0f;
	float outerUp = innerUp + 5.0f;
	for (int attempts = 0; particles > 0 && attempts < 1000; attempts++) {
		CVector pos;
		int rnd = CGeneral::GetRandomNumber();
		pos.x = (int8)(rnd - 128) * innerFw / 110.0f;
		pos.y = (int8)((rnd >> 8) - 128) * innerFw / 110.0f;
		pos.z = 0.0f;
		if (DotProduct2D(pos, TheCamera.GetForward()) >= 0)
			continue;
		// was there any point in adding it here?
		pos += pMatrix->GetPosition();
		pos.z = 2.0f;
		float fw = Abs(DotProduct(pMatrix->GetForward(), pos - pMatrix->GetPosition()));
		if (fw >= outerFw)
			continue;
		float rg = Abs(DotProduct(pMatrix->GetRight(), pos - pMatrix->GetPosition()));
		if (rg >= outerRg)
			continue;
		float up = Abs(DotProduct(pMatrix->GetUp(), pos - pMatrix->GetPosition()));
		if (up >= outerUp)
			continue;
		if (fw > innerFw || rg > innerRg || up > innerUp) {
			CParticle::AddParticle(PARTICLE_STEAM2, pos, CVector(0.0f, 0.0f, 0.0f), nil, 4.0f, FoamColour, 1, 0, 0, 0);
			particles--;
		}
	}
}

void
CSpecialParticleStuff::StartBoatFoamAnimation()
{
	BoatFromStart = CTimer::GetTimeInMilliseconds();
}

void
CSpecialParticleStuff::UpdateBoatFoamAnimation(CMatrix* pMatrix)
{
	static int32 FrameInAnimation = 0;
	static float X, Y, Z, dX, dY, dZ;
	CreateFoamAroundObject(pMatrix, 107.0f, 24.1f, 30.5f, 2);
	uint32 prev = CTimer::GetPreviousTimeInMilliseconds();
	uint32 cur = CTimer::GetTimeInMilliseconds();
	if (FrameInAnimation != 0) {
		X += dX;
		Y += dY;
		Z += dZ;
		CVector pos = *pMatrix * CVector(X, Y, Z);
		CParticle::AddParticle(PARTICLE_STEAM_NY, pos, CVector(0.0f, 0.0f, 0.0f),
			nil, FrameInAnimation * 0.5f + 2.0f, FoamColour, 1, 0, 0, 0);
		if (++FrameInAnimation > 15)
			FrameInAnimation = 0;
	}
	if ((cur & 0x3FF) < (prev & 0x3FF)) {
		FrameInAnimation = 1;
		int rnd = CGeneral::GetRandomNumber();
		X = (int8)(rnd - 128) * 0.2f;
		Y = (int8)((rnd >> 8) - 128) * 0.2f;
		Z = 10.0f;
		rnd = CGeneral::GetRandomNumber();
		dX = (int8)(rnd - 128) * 0.02f;
		dY = (int8)((rnd >> 8) - 128) * 0.02f;
		dZ = 2.0f;
	}
}