summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
author_AG <gennariarmando@outlook.com>2019-07-07 15:16:54 +0200
committer_AG <gennariarmando@outlook.com>2019-07-07 15:16:54 +0200
commitd1c6a6aaa6c17250e069d1267b27e13303d6e20f (patch)
tree76d55bfd8bcc8f72cdd4d261c0bb1eaa050e522a /src/core
parentMerge branch 'master' into master (diff)
parentthe great reorganization (diff)
downloadre3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar.gz
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar.bz2
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar.lz
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar.xz
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.tar.zst
re3-d1c6a6aaa6c17250e069d1267b27e13303d6e20f.zip
Diffstat (limited to 'src/core')
-rw-r--r--src/core/Camera.cpp1310
-rw-r--r--src/core/Camera.h477
-rw-r--r--src/core/CdStream.cpp529
-rw-r--r--src/core/CdStream.h46
-rw-r--r--src/core/Clock.cpp131
-rw-r--r--src/core/Clock.h31
-rw-r--r--src/core/Collision.cpp1887
-rw-r--r--src/core/Collision.h157
-rw-r--r--src/core/ControllerConfig.cpp57
-rw-r--r--src/core/ControllerConfig.h58
-rw-r--r--src/core/CutsceneMgr.cpp7
-rw-r--r--src/core/CutsceneMgr.h15
-rw-r--r--src/core/Directory.cpp65
-rw-r--r--src/core/Directory.h22
-rw-r--r--src/core/FileLoader.cpp1182
-rw-r--r--src/core/FileLoader.h42
-rw-r--r--src/core/FileMgr.cpp300
-rw-r--r--src/core/FileMgr.h21
-rw-r--r--src/core/Fire.cpp5
-rw-r--r--src/core/Fire.h23
-rw-r--r--src/core/Frontend.cpp2353
-rw-r--r--src/core/Frontend.h512
-rw-r--r--src/core/Game.cpp23
-rw-r--r--src/core/Game.h37
-rw-r--r--src/core/General.h91
-rw-r--r--src/core/Lists.cpp26
-rw-r--r--src/core/Lists.h130
-rw-r--r--src/core/MenuScreens.h380
-rw-r--r--src/core/Messages.cpp15
-rw-r--r--src/core/Messages.h44
-rw-r--r--src/core/NodeName.cpp77
-rw-r--r--src/core/NodeName.h4
-rw-r--r--src/core/PCSave.cpp20
-rw-r--r--src/core/PCSave.h21
-rw-r--r--src/core/Pad.cpp2091
-rw-r--r--src/core/Pad.h367
-rw-r--r--src/core/Placeable.cpp72
-rw-r--r--src/core/Placeable.h26
-rw-r--r--src/core/PlayerInfo.cpp5
-rw-r--r--src/core/PlayerInfo.h72
-rw-r--r--src/core/PlayerSkin.cpp5
-rw-r--r--src/core/PlayerSkin.h7
-rw-r--r--src/core/Pools.cpp25
-rw-r--r--src/core/Pools.h43
-rw-r--r--src/core/Radar.cpp1117
-rw-r--r--src/core/Radar.h146
-rw-r--r--src/core/References.cpp65
-rw-r--r--src/core/References.h20
-rw-r--r--src/core/RwClumpRead.cpp230
-rw-r--r--src/core/RwHelper.cpp356
-rw-r--r--src/core/RwHelper.h27
-rw-r--r--src/core/RwMatFX.cpp212
-rw-r--r--src/core/RwTexRead.cpp126
-rw-r--r--src/core/Stats.cpp13
-rw-r--r--src/core/Stats.h14
-rw-r--r--src/core/Streaming.cpp2509
-rw-r--r--src/core/Streaming.h188
-rw-r--r--src/core/SurfaceTable.cpp150
-rw-r--r--src/core/SurfaceTable.h106
-rw-r--r--src/core/TempColModels.cpp17
-rw-r--r--src/core/TempColModels.h21
-rw-r--r--src/core/Text.cpp232
-rw-r--r--src/core/Text.h52
-rw-r--r--src/core/Timer.cpp235
-rw-r--r--src/core/Timer.h51
-rw-r--r--src/core/TxdStore.cpp208
-rw-r--r--src/core/TxdStore.h44
-rw-r--r--src/core/User.cpp176
-rw-r--r--src/core/User.h64
-rw-r--r--src/core/Wanted.cpp129
-rw-r--r--src/core/Wanted.h49
-rw-r--r--src/core/World.cpp718
-rw-r--r--src/core/World.h119
-rw-r--r--src/core/ZoneCull.cpp370
-rw-r--r--src/core/ZoneCull.h94
-rw-r--r--src/core/Zones.cpp874
-rw-r--r--src/core/Zones.h112
-rw-r--r--src/core/common.h178
-rw-r--r--src/core/config.h116
-rw-r--r--src/core/debugmenu_public.h154
-rw-r--r--src/core/main.cpp891
-rw-r--r--src/core/main.h22
-rw-r--r--src/core/patcher.cpp22
-rw-r--r--src/core/patcher.h192
-rw-r--r--src/core/re3.cpp381
-rw-r--r--src/core/rw.cpp415
-rw-r--r--src/core/templates.h205
87 files changed, 24231 insertions, 0 deletions
diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp
new file mode 100644
index 00000000..58e65d24
--- /dev/null
+++ b/src/core/Camera.cpp
@@ -0,0 +1,1310 @@
+#include "common.h"
+#include "patcher.h"
+#include "main.h"
+#include "Draw.h"
+#include "World.h"
+#include "Vehicle.h"
+#include "Ped.h"
+#include "PlayerPed.h"
+#include "Pad.h"
+#include "General.h"
+#include "ZoneCull.h"
+#include "SurfaceTable.h"
+#include "MBlur.h"
+#include "Camera.h"
+
+const float DefaultFOV = 70.0f; // beta: 80.0f
+
+CCamera &TheCamera = *(CCamera*)0x6FACF8;
+bool &CCamera::m_bUseMouse3rdPerson = *(bool *)0x5F03D8;
+
+WRAPPER void CCamera::DrawBordersForWideScreen(void) { EAXJMP(0x46B430); }
+WRAPPER void CCamera::CalculateDerivedValues(void) { EAXJMP(0x46EEA0); }
+WRAPPER void CCamera::Restore(void) { EAXJMP(0x46F990); }
+WRAPPER void CCamera::SetWidescreenOff(void) { EAXJMP(0x46FF10); }
+
+bool
+CCamera::IsSphereVisible(const CVector &center, float radius, const CMatrix *mat)
+{
+ RwV3d c;
+ c = *(RwV3d*)&center;
+ RwV3dTransformPoints(&c, &c, 1, &mat->m_matrix);
+ if(c.y + radius < CDraw::GetNearClipZ()) return false;
+ if(c.y - radius > CDraw::GetFarClipZ()) return false;
+ if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > radius) return false;
+ if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > radius) return false;
+ if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > radius) return false;
+ if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > radius) return false;
+ return true;
+}
+
+bool
+CCamera::IsPointVisible(const CVector &center, const CMatrix *mat)
+{
+ RwV3d c;
+ c = *(RwV3d*)&center;
+ RwV3dTransformPoints(&c, &c, 1, &mat->m_matrix);
+ if(c.y < CDraw::GetNearClipZ()) return false;
+ if(c.y > CDraw::GetFarClipZ()) return false;
+ if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > 0.0f) return false;
+ if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > 0.0f) return false;
+ if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > 0.0f) return false;
+ if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > 0.0f) return false;
+ return true;
+}
+
+bool
+CCamera::IsBoxVisible(RwV3d *box, const CMatrix *mat)
+{
+ int i;
+ int frustumTests[6] = { 0 };
+ RwV3dTransformPoints(box, box, 8, &mat->m_matrix);
+
+ for(i = 0; i < 8; i++){
+ if(box[i].y < CDraw::GetNearClipZ()) frustumTests[0]++;
+ if(box[i].y > CDraw::GetFarClipZ()) frustumTests[1]++;
+ if(box[i].x*m_vecFrustumNormals[0].x + box[i].y*m_vecFrustumNormals[0].y > 0.0f) frustumTests[2]++;
+ if(box[i].x*m_vecFrustumNormals[1].x + box[i].y*m_vecFrustumNormals[1].y > 0.0f) frustumTests[3]++;
+// Why not test z?
+// if(box[i].y*m_vecFrustumNormals[2].y + box[i].z*m_vecFrustumNormals[2].z > 0.0f) frustumTests[4]++;
+// if(box[i].y*m_vecFrustumNormals[3].y + box[i].z*m_vecFrustumNormals[3].z > 0.0f) frustumTests[5]++;
+ }
+ for(i = 0; i < 6; i++)
+ if(frustumTests[i] == 8)
+ return false; // Box is completely outside of one plane
+ return true;
+}
+
+int
+CCamera::GetLookDirection(void)
+{
+ if(Cams[ActiveCam].Mode == CCam::MODE_CAMONASTRING ||
+ Cams[ActiveCam].Mode == CCam::MODE_FIRSTPERSON ||
+ Cams[ActiveCam].Mode == CCam::MODE_BEHINDBOAT ||
+ Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED)
+ return Cams[ActiveCam].DirectionWasLooking;
+ return LOOKING_FORWARD;;
+}
+
+WRAPPER void CCamera::Fade(float timeout, int16 direction) { EAXJMP(0x46B3A0); }
+WRAPPER void CCamera::ProcessFade(void) { EAXJMP(0x46F080); }
+WRAPPER void CCamera::ProcessMusicFade(void) { EAXJMP(0x46F1E0); }
+
+int
+CCamera::GetScreenFadeStatus(void)
+{
+ if(m_fFLOATingFade == 0.0f)
+ return FADE_0;
+ if(m_fFLOATingFade == 255.0f)
+ return FADE_2;
+ return FADE_1;
+}
+
+void
+CCamera::SetFadeColour(uint8 r, uint8 g, uint8 b)
+{
+ m_FadeTargetIsSplashScreen = r == 0 && g == 0 && b == 0;
+ CDraw::FadeRed = r;
+ CDraw::FadeGreen = g;
+ CDraw::FadeBlue = b;
+}
+
+void
+CCamera::SetMotionBlur(int r, int g, int b, int a, int type)
+{
+ m_BlurRed = r;
+ m_BlurGreen = g;
+ m_BlurBlue = b;
+ m_motionBlur = a;
+ m_BlurType = type;
+}
+
+void
+CCamera::SetMotionBlurAlpha(int a)
+{
+ m_imotionBlurAddAlpha = a;
+}
+
+void
+CCamera::RenderMotionBlur(void)
+{
+ if(m_BlurType == 0)
+ return;
+
+ CMBlur::MotionBlurRender(m_pRwCamera,
+ m_BlurRed, m_BlurGreen, m_BlurBlue,
+ m_motionBlur, m_BlurType, m_imotionBlurAddAlpha);
+}
+
+void
+CCamera::ClearPlayerWeaponMode()
+{
+ PlayerWeaponMode.Mode = 0;
+ PlayerWeaponMode.MaxZoom = 1;
+ PlayerWeaponMode.MinZoom = -1;
+ PlayerWeaponMode.Duration = 0.0f;
+}
+
+
+/*
+ *
+ * CCam
+ *
+ */
+
+
+// MaxSpeed is a limit of how fast the value is allowed to change. 1.0 = to Target in up to 1ms
+// Acceleration is how fast the speed will change to MaxSpeed. 1.0 = to MaxSpeed in 1ms
+void
+WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle)
+{
+ float Delta = Target - *CurrentValue;
+
+ if(IsAngle){
+ while(Delta >= PI) Delta -= 2*PI;
+ while(Delta < -PI) Delta += 2*PI;
+ }
+
+ float TargetSpeed = Delta * MaxSpeed;
+ // Add or subtract absolute depending on sign, genius!
+// if(TargetSpeed - *CurrentSpeed > 0.0f)
+// *CurrentSpeed += Acceleration * fabs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep();
+// else
+// *CurrentSpeed -= Acceleration * fabs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep();
+ // this is simpler:
+ *CurrentSpeed += Acceleration * (TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep();
+
+ // Clamp speed if we overshot
+ if(TargetSpeed < 0.0f && *CurrentSpeed < TargetSpeed)
+ *CurrentSpeed = TargetSpeed;
+ else if(TargetSpeed > 0.0f && *CurrentSpeed > TargetSpeed)
+ *CurrentSpeed = TargetSpeed;
+
+ *CurrentValue += *CurrentSpeed * min(10.0f, CTimer::GetTimeStep());
+}
+
+void
+CCam::GetVectorsReadyForRW(void)
+{
+ CVector right;
+ Up = CVector(0.0f, 0.0f, 1.0f);
+ Front.Normalise();
+ if(Front.x == 0.0f && Front.y == 0.0f){
+ Front.x = 0.0001f;
+ Front.y = 0.0001f;
+ }
+ right = CrossProduct(Front, Up);
+ right.Normalise();
+ Up = CrossProduct(right, Front);
+}
+
+// This code is really bad. wtf R*?
+CVector
+CCam::DoAverageOnVector(const CVector &vec)
+{
+ int i;
+ CVector Average = { 0.0f, 0.0f, 0.0f };
+
+ if(ResetStatics){
+ m_iRunningVectorArrayPos = 0;
+ m_iRunningVectorCounter = 1;
+ }
+
+ // TODO: make this work with NUMBER_OF_VECTORS_FOR_AVERAGE != 2
+ if(m_iRunningVectorCounter == 3){
+ m_arrPreviousVectors[0] = m_arrPreviousVectors[1];
+ m_arrPreviousVectors[1] = vec;
+ }else
+ m_arrPreviousVectors[m_iRunningVectorArrayPos] = vec;
+
+ for(i = 0; i <= m_iRunningVectorArrayPos; i++)
+ Average += m_arrPreviousVectors[i];
+ Average /= i;
+
+ m_iRunningVectorArrayPos++;
+ m_iRunningVectorCounter++;
+ if(m_iRunningVectorArrayPos >= NUMBER_OF_VECTORS_FOR_AVERAGE)
+ m_iRunningVectorArrayPos = NUMBER_OF_VECTORS_FOR_AVERAGE-1;
+ if(m_iRunningVectorCounter > NUMBER_OF_VECTORS_FOR_AVERAGE+1)
+ m_iRunningVectorCounter = NUMBER_OF_VECTORS_FOR_AVERAGE+1;
+
+ return Average;
+}
+
+// Rotate Beta in direction opposite of BetaOffset in 5 deg. steps.
+// Return the first angle for which Beta + BetaOffset + Angle has a clear view.
+// i.e. BetaOffset is a safe zone so that Beta + Angle is really clear.
+// If BetaOffset == 0, try both directions.
+float
+CCam::GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies)
+{
+ CColPoint point;
+ CEntity *ent = nil;
+ CVector ToSource;
+ float a;
+
+ // This would be so much nicer if we just got the step variable before the loop...R*
+
+ for(a = 0.0f; a <= PI; a += DEGTORAD(5.0f)){
+ if(BetaOffset <= 0.0f){
+ ToSource = CVector(cos(Beta + BetaOffset + a), sin(Beta + BetaOffset + a), 0.0f)*Dist;
+ if(!CWorld::ProcessLineOfSight(Target, Target + ToSource,
+ point, ent, checkBuildings, checkVehicles, checkPeds,
+ checkObjects, checkDummies, true, true))
+ return a;
+ }
+ if(BetaOffset >= 0.0f){
+ ToSource = CVector(cos(Beta + BetaOffset - a), sin(Beta + BetaOffset - a), 0.0f)*Dist;
+ if(!CWorld::ProcessLineOfSight(Target, Target + ToSource,
+ point, ent, checkBuildings, checkVehicles, checkPeds,
+ checkObjects, checkDummies, true, true))
+ return -a;
+ }
+ }
+ return 0.0f;
+}
+
+static float DefaultAcceleration = 0.045f;
+static float DefaultMaxStep = 0.15f;
+
+void
+CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float)
+{
+ const float GroundDist = 1.85f;
+
+ CVector TargetCoors, Dist, IdealSource;
+ float Length = 0.0f;
+ float LateralLeft = 0.0f;
+ float LateralRight = 0.0f;
+ float Center = 0.0f;
+ static bool PreviouslyObscured;
+ static bool PickedASide;
+ static float FixedTargetOrientation = 0.0f;
+ float AngleToGoTo = 0.0f;
+ float BetaOffsetAvoidBuildings = 0.45f; // ~25 deg
+ float BetaOffsetGoingBehind = 0.45f;
+ bool GoingBehind = false;
+ bool Obscured = false;
+ bool BuildingCheckObscured = false;
+ bool HackPlayerOnStoppingTrain = false;
+ static int TimeIndicatedWantedToGoDown = 0;
+ static bool StartedCountingForGoDown = false;
+ float DeltaBeta;
+
+ m_bFixingBeta = false;
+ bBelowMinDist = false;
+ bBehindPlayerDesired = false;
+
+ assert(CamTargetEntity->IsPed());
+
+ // CenterDist should be > LateralDist because we don't have an angle for safety in this case
+ float CenterDist, LateralDist;
+ float AngleToGoToSpeed;
+ if(m_fCloseInPedHeightOffsetSpeed > 0.00001f){
+ LateralDist = 0.55f;
+ CenterDist = 1.25f;
+ BetaOffsetAvoidBuildings = 0.9f; // ~50 deg
+ BetaOffsetGoingBehind = 0.9f;
+ AngleToGoToSpeed = 0.88254666f;
+ }else{
+ LateralDist = 0.8f;
+ CenterDist = 1.35f;
+ if(TheCamera.PedZoomIndicator == 1.0f || TheCamera.PedZoomIndicator == 4.0f){
+ LateralDist = 1.25f;
+ CenterDist = 1.6f;
+ }
+ AngleToGoToSpeed = 0.43254671f;
+ }
+
+ FOV = DefaultFOV;
+
+ if(ResetStatics){
+ Rotating = false;
+ m_bCollisionChecksOn = true;
+ FixedTargetOrientation = 0.0f;
+ PreviouslyObscured = false;
+ PickedASide = false;
+ StartedCountingForGoDown = false;
+ AngleToGoTo = 0.0f;
+ // unused LastAngleWithNoPickedASide
+ }
+
+
+ TargetCoors = CameraTarget;
+ IdealSource = Source;
+ TargetCoors.z += m_fSyphonModeTargetZOffSet;
+
+ CVector TempTargetCoors;
+ TempTargetCoors = DoAverageOnVector(TargetCoors);
+ TargetCoors = TempTargetCoors;
+ // Add this unknown offset, but later it's removed again
+ TargetCoors.z += m_fUnknownZOffSet;
+
+ Dist.x = IdealSource.x - TargetCoors.x;
+ Dist.y = IdealSource.y - TargetCoors.y;
+ Length = Dist.Magnitude2D();
+
+ // Cam on a string. With a fixed distance. Zoom in/out is done later.
+ if(Length != 0.0f)
+ IdealSource = TargetCoors + CVector(Dist.x, Dist.y, 0.0f)/Length * GroundDist;
+ else
+ IdealSource = TargetCoors + CVector(1.0f, 1.0f, 0.0f);
+
+ // TODO: what's transition beta?
+ if(TheCamera.m_bUseTransitionBeta && ResetStatics){
+ CVector VecDistance;
+ IdealSource.x = TargetCoors.x + GroundDist*cos(m_fTransitionBeta);
+ IdealSource.y = TargetCoors.y + GroundDist*sin(m_fTransitionBeta);
+ Beta = CGeneral::GetATanOfXY(IdealSource.x - TargetCoors.x, IdealSource.y - TargetCoors.y);
+ }else
+ Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y);
+
+ if(TheCamera.m_bCamDirectlyBehind){
+ m_bCollisionChecksOn = true;
+ Beta = TargetOrientation + PI;
+ }
+
+ if(FindPlayerVehicle())
+ if(FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_TRAIN)
+ HackPlayerOnStoppingTrain = true;
+
+ if(TheCamera.m_bCamDirectlyInFront){
+ m_bCollisionChecksOn = true;
+ Beta = TargetOrientation;
+ }
+
+ while(Beta >= PI) Beta -= 2.0f * PI;
+ while(Beta < -PI) Beta += 2.0f * PI;
+
+ // BUG? is this ever used?
+ // The values seem to be roughly m_fPedZoomValueSmooth + 1.85
+ if(ResetStatics){
+ if(TheCamera.PedZoomIndicator == 1.0) m_fRealGroundDist = 2.090556f;
+ if(TheCamera.PedZoomIndicator == 2.0) m_fRealGroundDist = 3.34973f;
+ if(TheCamera.PedZoomIndicator == 3.0) m_fRealGroundDist = 4.704914f;
+ if(TheCamera.PedZoomIndicator == 4.0) m_fRealGroundDist = 2.090556f;
+ }
+ // And what is this? It's only used for collision and rotation it seems
+ float RealGroundDist;
+ if(TheCamera.PedZoomIndicator == 1.0) RealGroundDist = 2.090556f;
+ if(TheCamera.PedZoomIndicator == 2.0) RealGroundDist = 3.34973f;
+ if(TheCamera.PedZoomIndicator == 3.0) RealGroundDist = 4.704914f;
+ if(TheCamera.PedZoomIndicator == 4.0) RealGroundDist = 2.090556f;
+ if(m_fCloseInPedHeightOffset > 0.00001f)
+ RealGroundDist = 1.7016;
+
+
+ bool Shooting = false;
+ CPed *ped = (CPed*)CamTargetEntity;
+ if(ped->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED)
+ if(CPad::GetPad(0)->GetWeapon())
+ Shooting = true;
+ if(ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR ||
+ ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT)
+ Shooting = false;
+
+
+ if(m_fCloseInPedHeightOffset > 0.00001f)
+ TargetCoors.z -= m_fUnknownZOffSet;
+
+ // Figure out if and where we want to rotate
+
+ if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){
+
+ // Center cam behind player
+
+ GoingBehind = true;
+ m_bCollisionChecksOn = true;
+ float OriginalBeta = Beta;
+ // Set Beta behind player
+ Beta = TargetOrientation + PI;
+ TargetCoors.z -= 0.1f;
+
+ AngleToGoTo = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false);
+ if(AngleToGoTo != 0.0f){
+ if(AngleToGoTo < 0.0f)
+ AngleToGoTo -= AngleToGoToSpeed;
+ else
+ AngleToGoTo += AngleToGoToSpeed;
+ }else{
+ float LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetGoingBehind, true, false, false, true, false);
+ float LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetGoingBehind, true, false, false, true, false);
+ if(LateralLeft == 0.0f && LateralRight != 0.0f)
+ AngleToGoTo += LateralRight;
+ else if(LateralLeft != 0.0f && LateralRight == 0.0f)
+ AngleToGoTo += LateralLeft;
+ }
+
+ TargetCoors.z += 0.1f;
+ Beta = OriginalBeta;
+
+ if(PickedASide){
+ if(AngleToGoTo == 0.0f)
+ FixedTargetOrientation = TargetOrientation + PI;
+ Rotating = true;
+ }else{
+ FixedTargetOrientation = TargetOrientation + PI + AngleToGoTo;
+ Rotating = true;
+ PickedASide = true;
+ }
+ }else{
+
+ // Rotate cam to avoid clipping into buildings
+
+ TargetCoors.z -= 0.1f;
+
+ Center = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false);
+ if(m_bCollisionChecksOn || PreviouslyObscured || Center != 0.0f || m_fCloseInPedHeightOffset > 0.00001f){
+ if(Center != 0.0f){
+ AngleToGoTo = Center;
+ }else{
+ LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetAvoidBuildings, true, false, false, true, false);
+ LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetAvoidBuildings, true, false, false, true, false);
+ if(LateralLeft == 0.0f && LateralRight != 0.0f){
+ AngleToGoTo += LateralRight;
+ if(m_fCloseInPedHeightOffset > 0.0f)
+ RwCameraSetNearClipPlane(Scene.camera, 0.7f);
+ }else if(LateralLeft != 0.0f && LateralRight == 0.0f){
+ AngleToGoTo += LateralLeft;
+ if(m_fCloseInPedHeightOffset > 0.0f)
+ RwCameraSetNearClipPlane(Scene.camera, 0.7f);
+ }
+ }
+ if(LateralLeft != 0.0f || LateralRight != 0.0f || Center != 0.0f)
+ BuildingCheckObscured = true;
+ }
+
+ TargetCoors.z += 0.1f;
+ }
+
+ if(m_fCloseInPedHeightOffset > 0.00001f)
+ TargetCoors.z += m_fUnknownZOffSet;
+
+
+ // Have to fix to avoid collision
+
+ if(AngleToGoTo != 0.0f){
+ Obscured = true;
+ Rotating = true;
+ if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){
+ if(!PickedASide)
+ FixedTargetOrientation = Beta + AngleToGoTo; // can this even happen?
+ }else
+ FixedTargetOrientation = Beta + AngleToGoTo;
+
+ // This calculation is only really used to figure out how fast to rotate out of collision
+
+ m_fAmountFractionObscured = 1.0f;
+ CVector PlayerPos = FindPlayerPed()->GetPosition();
+ float RotationDist = (AngleToGoTo == Center ? CenterDist : LateralDist) * RealGroundDist;
+ // What's going on here? - AngleToGoTo?
+ CVector RotatedSource = PlayerPos + CVector(cos(Beta - AngleToGoTo), sin(Beta - AngleToGoTo), 0.0f) * RotationDist;
+
+ CColPoint colpoint;
+ CEntity *entity;
+ if(CWorld::ProcessLineOfSight(PlayerPos, RotatedSource, colpoint, entity, true, false, false, true, false, false, false)){
+ if((PlayerPos - RotatedSource).Magnitude() != 0.0f)
+ m_fAmountFractionObscured = (PlayerPos - colpoint.point).Magnitude() / (PlayerPos - RotatedSource).Magnitude();
+ else
+ m_fAmountFractionObscured = 1.0f;
+ }
+ }
+ if(m_fAmountFractionObscured < 0.0f) m_fAmountFractionObscured = 0.0f;
+ if(m_fAmountFractionObscured > 1.0f) m_fAmountFractionObscured = 1.0f;
+
+
+
+ // Figure out speed values for Beta rotation
+
+ float Acceleration, MaxSpeed;
+ static float AccelerationMult = 0.35f;
+ static float MaxSpeedMult = 0.85f;
+ static float AccelerationMultClose = 0.7f;
+ static float MaxSpeedMultClose = 1.6f;
+ float BaseAcceleration = 0.025f;
+ float BaseMaxSpeed = 0.09f;
+ if(m_fCloseInPedHeightOffset > 0.00001f){
+ if(AngleToGoTo == 0.0f){
+ BaseAcceleration = 0.022f;
+ BaseMaxSpeed = 0.04f;
+ }else{
+ BaseAcceleration = DefaultAcceleration;
+ BaseMaxSpeed = DefaultMaxStep;
+ }
+ }
+ if(AngleToGoTo == 0.0f){
+ Acceleration = BaseAcceleration;
+ MaxSpeed = BaseMaxSpeed;
+ }else if(CPad::GetPad(0)->ForceCameraBehindPlayer() && !Shooting){
+ Acceleration = 0.051f;
+ MaxSpeed = 0.18f;
+ }else if(m_fCloseInPedHeightOffset > 0.00001f){
+ Acceleration = BaseAcceleration + AccelerationMultClose*sq(m_fAmountFractionObscured - 1.05f);
+ MaxSpeed = BaseMaxSpeed + MaxSpeedMultClose*sq(m_fAmountFractionObscured - 1.05f);
+ }else{
+ Acceleration = DefaultAcceleration + AccelerationMult*sq(m_fAmountFractionObscured - 1.05f);
+ MaxSpeed = DefaultMaxStep + MaxSpeedMult*sq(m_fAmountFractionObscured - 1.05f);
+ }
+ static float AccelerationLimit = 0.3f;
+ static float MaxSpeedLimit = 0.65f;
+ if(Acceleration > AccelerationLimit) Acceleration = AccelerationLimit;
+ if(MaxSpeed > MaxSpeedLimit) MaxSpeed = MaxSpeedLimit;
+
+
+ int MoveState = ((CPed*)CamTargetEntity)->m_nMoveState;
+ if(MoveState != PEDMOVE_NONE && MoveState != PEDMOVE_STILL &&
+ !CPad::GetPad(0)->ForceCameraBehindPlayer() && !Obscured && !Shooting){
+ Rotating = false;
+ BetaSpeed = 0.0f;
+ }
+
+ // Now do the Beta rotation
+
+ float Distance = (IdealSource - TargetCoors).Magnitude2D();
+ m_fDistanceBeforeChanges = Distance;
+
+ if(Rotating){
+ m_bFixingBeta = true;
+
+ while(FixedTargetOrientation >= PI) FixedTargetOrientation -= 2*PI;
+ while(FixedTargetOrientation < -PI) FixedTargetOrientation += 2*PI;
+
+ while(Beta >= PI) Beta -= 2*PI;
+ while(Beta < -PI) Beta += 2*PI;
+
+
+/*
+ // This is inlined WellBufferMe
+ DeltaBeta = FixedTargetOrientation - Beta;
+ while(DeltaBeta >= PI) DeltaBeta -= 2*PI;
+ while(DeltaBeta < -PI) DeltaBeta += 2*PI;
+
+ float ReqSpeed = DeltaBeta * MaxSpeed;
+ // Add or subtract absolute depending on sign, genius!
+ if(ReqSpeed - BetaSpeed > 0.0f)
+ BetaSpeed += SpeedStep * fabs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep();
+ else
+ BetaSpeed -= SpeedStep * fabs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep();
+ // this would be simpler:
+ // BetaSpeed += SpeedStep * (ReqSpeed - BetaSpeed) * CTimer::ms_fTimeStep;
+
+ if(ReqSpeed < 0.0f && BetaSpeed < ReqSpeed)
+ BetaSpeed = ReqSpeed;
+ else if(ReqSpeed > 0.0f && BetaSpeed > ReqSpeed)
+ BetaSpeed = ReqSpeed;
+
+ Beta += BetaSpeed * min(10.0f, CTimer::GetTimeStep());
+*/
+ WellBufferMe(FixedTargetOrientation, &Beta, &BetaSpeed, MaxSpeed, Acceleration, true);
+
+ if(ResetStatics){
+ Beta = FixedTargetOrientation;
+ BetaSpeed = 0.0f;
+ }
+
+ Source.x = TargetCoors.x + Distance * cos(Beta);
+ Source.y = TargetCoors.y + Distance * sin(Beta);
+
+ // Check if we can stop rotating
+ DeltaBeta = FixedTargetOrientation - Beta;
+ while(DeltaBeta >= PI) DeltaBeta -= 2*PI;
+ while(DeltaBeta < -PI) DeltaBeta += 2*PI;
+ if(fabs(DeltaBeta) < DEGTORAD(1.0f) && !bBehindPlayerDesired){
+ // Stop rotation
+ PickedASide = false;
+ Rotating = false;
+ BetaSpeed = 0.0f;
+ }
+ }
+
+
+ if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront ||
+ HackPlayerOnStoppingTrain || Rotating){
+ if(TheCamera.m_bCamDirectlyBehind){
+ Beta = TargetOrientation + PI;
+ Source.x = TargetCoors.x + Distance * cos(Beta);
+ Source.y = TargetCoors.y + Distance * sin(Beta);
+ }
+ if(TheCamera.m_bCamDirectlyInFront){
+ Beta = TargetOrientation;
+ Source.x = TargetCoors.x + Distance * cos(Beta);
+ Source.y = TargetCoors.y + Distance * sin(Beta);
+ }
+ if(HackPlayerOnStoppingTrain){
+ Beta = TargetOrientation + PI;
+ Source.x = TargetCoors.x + Distance * cos(Beta);
+ Source.y = TargetCoors.y + Distance * sin(Beta);
+ m_fDimensionOfHighestNearCar = 0.0f;
+ m_fCamBufferedHeight = 0.0f;
+ m_fCamBufferedHeightSpeed = 0.0f;
+ }
+ // Beta and Source already set in the rotation code
+ }else{
+ Source = IdealSource;
+ BetaSpeed = 0.0f;
+ }
+
+ // Subtract m_fUnknownZOffSet from both?
+ TargetCoors.z -= m_fUnknownZOffSet;
+ Source.z = IdealSource.z - m_fUnknownZOffSet;
+
+ // Apply zoom now
+ // m_fPedZoomValueSmooth makes the cam go down the further out it is
+ // 0.25 -> 0.20 for nearest dist
+ // 1.50 -> -0.05 for mid dist
+ // 2.90 -> -0.33 for far dist
+ Source.z += (2.5f - TheCamera.m_fPedZoomValueSmooth)*0.2f - 0.25f;
+ // Zoom out camera
+ Front = TargetCoors - Source;
+ Front.Normalise();
+ Source -= Front * TheCamera.m_fPedZoomValueSmooth;
+ // and then we move up again
+ // -0.375
+ // 0.25
+ // 0.95
+ Source.z += (TheCamera.m_fPedZoomValueSmooth - 1.0f)*0.5f + m_fCloseInPedHeightOffset;
+
+
+ // Process height offset to avoid peds and cars
+
+ float TargetZOffSet = m_fUnknownZOffSet + m_fDimensionOfHighestNearCar;
+ TargetZOffSet = max(TargetZOffSet, m_fPedBetweenCameraHeightOffset);
+ float TargetHeight = CameraTarget.z + TargetZOffSet - Source.z;
+
+ if(TargetHeight > m_fCamBufferedHeight){
+ // Have to go up
+ if(TargetZOffSet == m_fPedBetweenCameraHeightOffset && TargetZOffSet > m_fCamBufferedHeight)
+ WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.04f, false);
+ else if(TargetZOffSet == m_fUnknownZOffSet && TargetZOffSet > m_fCamBufferedHeight){
+ // TODO: figure this out
+ bool foo = false;
+ switch(((CPhysical*)CamTargetEntity)->m_nLastCollType)
+ case 2: case 3: case 5:
+ case 11: case 23: case 26:
+ foo = true;
+ if(foo)
+ WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.4f, 0.05f, false);
+ else
+ WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false);
+ }else
+ WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false);
+ StartedCountingForGoDown = false;
+ }else{
+ // Have to go down
+ if(StartedCountingForGoDown){
+ if(CTimer::GetTimeInMilliseconds() != TimeIndicatedWantedToGoDown){
+ if(TargetHeight > 0.0f)
+ WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false);
+ else
+ WellBufferMe(0.0f, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false);
+ }
+ }else{
+ StartedCountingForGoDown = true;
+ TimeIndicatedWantedToGoDown = CTimer::GetTimeInMilliseconds();
+ }
+ }
+
+ Source.z += m_fCamBufferedHeight;
+
+
+ // Clip Source if necessary
+
+ bool ClipSource = m_fCloseInPedHeightOffset > 0.00001f && m_fCamBufferedHeight > 0.001f;
+ if(GoingBehind || ResetStatics || ClipSource){
+ CColPoint colpoint;
+ CEntity *entity;
+ if(CWorld::ProcessLineOfSight(TargetCoors, Source, colpoint, entity, true, false, false, true, false, true, true)){
+ Source = colpoint.point;
+ if((TargetCoors - Source).Magnitude2D() < 1.0f)
+ RwCameraSetNearClipPlane(Scene.camera, 0.05f);
+ }
+ }
+
+ TargetCoors.z += min(1.0f, m_fCamBufferedHeight/2.0f);
+ m_cvecTargetCoorsForFudgeInter = TargetCoors;
+
+ Front = TargetCoors - Source;
+ m_fRealGroundDist = Front.Magnitude2D();
+ m_fMinDistAwayFromCamWhenInterPolating = m_fRealGroundDist;
+ Front.Normalise();
+ GetVectorsReadyForRW();
+ TheCamera.m_bCamDirectlyBehind = false;
+ TheCamera.m_bCamDirectlyInFront = false;
+ PreviouslyObscured = BuildingCheckObscured;
+
+ ResetStatics = false;
+}
+
+void
+CCam::Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float)
+{
+ FOV = DefaultFOV;
+
+ if(!CamTargetEntity->IsVehicle())
+ return;
+
+ CVector TargetCoors = CameraTarget;
+ TargetCoors.z -= 0.2f;
+ CA_MAX_DISTANCE = 9.95f;
+ CA_MIN_DISTANCE = 8.5f;
+
+ CVector Dist = Source - TargetCoors;
+ float Length = Dist.Magnitude2D();
+ m_fDistanceBeforeChanges = Length;
+ if(Length < 0.002f)
+ Length = 0.002f;
+ Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y);
+ if(Length > CA_MAX_DISTANCE){
+ Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE;
+ Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE;
+ }else if(Length < CA_MIN_DISTANCE){
+ Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE;
+ Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE;
+ }
+ TargetCoors.z += 0.8f;
+
+ WorkOutCamHeightWeeCar(TargetCoors, TargetOrientation);
+ RotCamIfInFrontCar(TargetCoors, TargetOrientation);
+ FixCamIfObscured(TargetCoors, 1.2f, TargetOrientation);
+
+ Front = TargetCoors - Source;
+ m_cvecTargetCoorsForFudgeInter = TargetCoors;
+ ResetStatics = false;
+ GetVectorsReadyForRW();
+}
+
+void
+CCam::WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation)
+{
+ CColPoint colpoint;
+ CEntity *ent;
+ float TargetZOffSet = 0.0f;
+ static bool PreviouslyFailedRoadHeightCheck = false;
+ static float RoadHeightFix = 0.0f;
+ static float RoadHeightFixSpeed = 0.0f;
+
+ if(ResetStatics){
+ RoadHeightFix = 0.0f;
+ RoadHeightFixSpeed = 0.0f;
+ Alpha = DEGTORAD(25.0f);
+ AlphaSpeed = 0.0f;
+ }
+ float AlphaTarget = DEGTORAD(25.0f);
+ if(CCullZones::CamNoRain() || CCullZones::PlayerNoRain())
+ AlphaTarget = DEGTORAD(14.0f);
+ WellBufferMe(AlphaTarget, &Alpha, &AlphaSpeed, 0.1f, 0.05f, true);
+ Source.z = TargetCoors.z + CA_MAX_DISTANCE*sin(Alpha);
+
+ if(FindPlayerVehicle()){
+ m_fUnknownZOffSet = 0.0f;
+ bool FoundRoad = false;
+ bool FoundRoof = false;
+ float RoadZ = 0.0f;
+ float RoofZ = 0.0f;
+
+ if(CWorld::ProcessVerticalLine(Source, -1000.0f, colpoint, ent, true, false, false, false, false, false, nil) &&
+ ent->IsBuilding()){
+ FoundRoad = true;
+ RoadZ = colpoint.point.z;
+ }
+
+ if(FoundRoad){
+ if(Source.z - RoadZ < 0.9f){
+ PreviouslyFailedRoadHeightCheck = true;
+ TargetZOffSet = RoadZ + 0.9f - Source.z;
+ }else{
+ if(m_bCollisionChecksOn)
+ PreviouslyFailedRoadHeightCheck = false;
+ else
+ TargetZOffSet = 0.0f;
+ }
+ }else{
+ if(CWorld::ProcessVerticalLine(Source, 1000.0f, colpoint, ent, true, false, false, false, false, false, nil) &&
+ ent->IsBuilding()){
+ FoundRoof = true;
+ RoofZ = colpoint.point.z;
+ }
+ if(FoundRoof){
+ if(Source.z - RoofZ < 0.9f){
+ PreviouslyFailedRoadHeightCheck = true;
+ TargetZOffSet = RoofZ + 0.9f - Source.z;
+ }else{
+ if(m_bCollisionChecksOn)
+ PreviouslyFailedRoadHeightCheck = false;
+ else
+ TargetZOffSet = 0.0f;
+ }
+ }
+ }
+ }
+
+ if(TargetZOffSet > RoadHeightFix)
+ RoadHeightFix = TargetZOffSet;
+ else
+ WellBufferMe(TargetZOffSet, &RoadHeightFix, &RoadHeightFixSpeed, 0.27f, 0.1f, false);
+
+ if((colpoint.surfaceB == SURFACE_DEFAULT || colpoint.surfaceB >= SURFACE_METAL6) &&
+ colpoint.surfaceB != SURFACE_STEEL && colpoint.surfaceB != SURFACE_STONE &&
+ RoadHeightFix > 1.4f)
+ RoadHeightFix = 1.4f;
+
+ Source.z += RoadHeightFix;
+}
+
+void
+CCam::WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight)
+{
+ static float LastTargetAlphaWithCollisionOn = 0.0f;
+ static float LastTopAlphaSpeed = 0.0f;
+ static float LastAlphaSpeedStep = 0.0f;
+ static bool PreviousNearCheckNearClipSmall = false;
+
+ bool CamClear = true;
+ float ModeAlpha = 0.0f;
+
+ if(ResetStatics){
+ LastTargetAlphaWithCollisionOn = 0.0f;
+ LastTopAlphaSpeed = 0.0f;
+ LastAlphaSpeedStep = 0.0f;
+ PreviousNearCheckNearClipSmall = false;
+ }
+
+ float TopAlphaSpeed = 0.15f;
+ float AlphaSpeedStep = 0.015f;
+
+ float zoomvalue = TheCamera.CarZoomValueSmooth;
+ if(zoomvalue < 0.1f)
+ zoomvalue = 0.1f;
+ if(TheCamera.CarZoomIndicator == 1.0f)
+ ModeAlpha = CGeneral::GetATanOfXY(23.0f, zoomvalue); // near
+ else if(TheCamera.CarZoomIndicator == 2.0f)
+ ModeAlpha = CGeneral::GetATanOfXY(10.8f, zoomvalue); // mid
+ else if(TheCamera.CarZoomIndicator == 3.0f)
+ ModeAlpha = CGeneral::GetATanOfXY(7.0f, zoomvalue); // far
+
+
+ float Length = (Source - TargetCoors).Magnitude2D();
+ if(m_bCollisionChecksOn){ // there's another variable (on PC) but it's uninitialised
+ CVector Forward = CamTargetEntity->GetForward();
+ float CarAlpha = CGeneral::GetATanOfXY(Forward.Magnitude2D(), Forward.z);
+ // this shouldn't be necessary....
+ while(CarAlpha >= PI) CarAlpha -= 2*PI;
+ while(CarAlpha < -PI) CarAlpha += 2*PI;
+
+ while(Beta >= PI) Beta -= 2*PI;
+ while(Beta < -PI) Beta += 2*PI;
+
+ float deltaBeta = Beta - TargetOrientation;
+ while(deltaBeta >= PI) deltaBeta -= 2*PI;
+ while(deltaBeta < -PI) deltaBeta += 2*PI;
+
+ float BehindCarNess = cos(deltaBeta); // 1 if behind car, 0 if side, -1 if in front
+ CarAlpha = -CarAlpha * BehindCarNess;
+ if(CarAlpha < -0.01f)
+ CarAlpha = -0.01f;
+
+ float DeltaAlpha = CarAlpha - Alpha;
+ while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI;
+ while(DeltaAlpha < -PI) DeltaAlpha += 2*PI;
+ // What's this?? wouldn't it make more sense to clamp?
+ float AngleLimit = DEGTORAD(1.8f);
+ if(DeltaAlpha < -AngleLimit)
+ DeltaAlpha += AngleLimit;
+ else if(DeltaAlpha > AngleLimit)
+ DeltaAlpha -= AngleLimit;
+ else
+ DeltaAlpha = 0.0f;
+
+ // Now the collision
+
+ float TargetAlpha = 0.0f;
+ bool FoundRoofCenter = false;
+ bool FoundRoofSide1 = false;
+ bool FoundRoofSide2 = false;
+ bool FoundCamRoof = false;
+ bool FoundCamGround = false;
+ float CamRoof = 0.0f;
+ float CarBottom = TargetCoors.z - TargetHeight/2.0f;
+
+ // Check car center
+ float CarRoof = CWorld::FindRoofZFor3DCoord(TargetCoors.x, TargetCoors.y, CarBottom, &FoundRoofCenter);
+
+ // Check sides of the car
+ Forward = CamTargetEntity->GetForward(); // we actually still have that...
+ Forward.Normalise(); // shouldn't be necessary
+ float CarSideAngle = CGeneral::GetATanOfXY(Forward.x, Forward.y) + PI/2.0f;
+ float SideX = 2.5f * cos(CarSideAngle);
+ float SideY = 2.5f * sin(CarSideAngle);
+ CWorld::FindRoofZFor3DCoord(TargetCoors.x + SideX, TargetCoors.y + SideY, CarBottom, &FoundRoofSide1);
+ CWorld::FindRoofZFor3DCoord(TargetCoors.x - SideX, TargetCoors.y - SideY, CarBottom, &FoundRoofSide2);
+
+ // Now find out at what height we'd like to place the camera
+ float CamGround = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, TargetCoors.z + Length*sin(Alpha + ModeAlpha) + m_fCloseInCarHeightOffset, &FoundCamGround);
+ float CamTargetZ = 0.0f;
+ if(FoundCamGround){
+ // This is the normal case
+ CamRoof = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamGround + TargetHeight, &FoundCamRoof);
+ CamTargetZ = CamGround + TargetHeight*1.5f + 0.1f;
+ }else{
+ FoundCamRoof = false;
+ CamTargetZ = TargetCoors.z;
+ }
+
+ if(FoundRoofCenter && !FoundCamRoof && (FoundRoofSide1 || FoundRoofSide2)){
+ // Car is under something but camera isn't
+ // This seems weird...
+ TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, CarRoof - CamTargetZ - 1.5f);
+ CamClear = false;
+ }
+ if(FoundCamRoof){
+ // Camera is under something
+ float roof = FoundRoofCenter ? min(CamRoof, CarRoof) : CamRoof;
+ // Same weirdness again?
+ TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, roof - CamTargetZ - 1.5f);
+ CamClear = false;
+ }
+ while(TargetAlpha >= PI) TargetAlpha -= 2*PI;
+ while(TargetAlpha < -PI) TargetAlpha += 2*PI;
+ if(TargetAlpha < DEGTORAD(-7.0f))
+ TargetAlpha = DEGTORAD(-7.0f);
+
+ // huh?
+ if(TargetAlpha > ModeAlpha)
+ CamClear = true;
+ // Camera is contrained by collision in some way
+ PreviousNearCheckNearClipSmall = false;
+ if(!CamClear){
+ PreviousNearCheckNearClipSmall = true;
+ RwCameraSetNearClipPlane(Scene.camera, 0.9f);
+
+ DeltaAlpha = TargetAlpha - (Alpha + ModeAlpha);
+ while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI;
+ while(DeltaAlpha < -PI) DeltaAlpha += 2*PI;
+
+ TopAlphaSpeed = 0.3f;
+ AlphaSpeedStep = 0.03f;
+ }
+
+ // Now do things if CamClear...but what is that anyway?
+ float CamZ = TargetCoors.z + Length*sin(Alpha + DeltaAlpha + ModeAlpha) + m_fCloseInCarHeightOffset;
+ bool FoundGround, FoundRoof;
+ float CamGround2 = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, CamZ, &FoundGround);
+ if(FoundGround){
+ if(CamClear)
+ if(CamZ - CamGround2 < 1.5f){
+ PreviousNearCheckNearClipSmall = true;
+ RwCameraSetNearClipPlane(Scene.camera, 0.9f);
+
+ float a;
+ if(Length == 0.0f || CamGround2 + 1.5f - TargetCoors.z == 0.0f)
+ a = Alpha;
+ else
+ a = CGeneral::GetATanOfXY(Length, CamGround2 + 1.5f - TargetCoors.z);
+ while(a > PI) a -= 2*PI;
+ while(a < -PI) a += 2*PI;
+ DeltaAlpha = a - Alpha;
+ }
+ }else{
+ if(CamClear){
+ float CamRoof2 = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamZ, &FoundRoof);
+ if(FoundRoof && CamZ - CamRoof2 < 1.5f){
+ PreviousNearCheckNearClipSmall = true;
+ RwCameraSetNearClipPlane(Scene.camera, 0.9f);
+
+ if(CamRoof2 > TargetCoors.z + 3.5f)
+ CamRoof2 = TargetCoors.z + 3.5f;
+
+ float a;
+ if(Length == 0.0f || CamRoof2 + 1.5f - TargetCoors.z == 0.0f)
+ a = Alpha;
+ else
+ a = CGeneral::GetATanOfXY(Length, CamRoof2 + 1.5f - TargetCoors.z);
+ while(a > PI) a -= 2*PI;
+ while(a < -PI) a += 2*PI;
+ DeltaAlpha = a - Alpha;
+ }
+ }
+ }
+
+ LastTargetAlphaWithCollisionOn = DeltaAlpha + Alpha;
+ LastTopAlphaSpeed = TopAlphaSpeed;
+ LastAlphaSpeedStep = AlphaSpeedStep;
+ }else{
+ if(PreviousNearCheckNearClipSmall)
+ RwCameraSetNearClipPlane(Scene.camera, 0.9f);
+ }
+
+ WellBufferMe(LastTargetAlphaWithCollisionOn, &Alpha, &AlphaSpeed, LastTopAlphaSpeed, LastAlphaSpeedStep, true);
+
+ Source.z = TargetCoors.z + sin(Alpha + ModeAlpha)*Length + m_fCloseInCarHeightOffset;
+}
+
+// Rotate cam behind the car when the car is moving forward
+bool
+CCam::RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation)
+{
+ bool MovingForward = false;
+ CPhysical *phys = (CPhysical*)CamTargetEntity;
+
+ float ForwardSpeed = DotProduct(phys->GetForward(), phys->GetSpeed(CVector(0.0f, 0.0f, 0.0f)));
+ if(ForwardSpeed > 0.02f)
+ MovingForward = true;
+
+ float Dist = (Source - TargetCoors).Magnitude2D();
+
+ float DeltaBeta = TargetOrientation - Beta;
+ while(DeltaBeta >= PI) DeltaBeta -= 2*PI;
+ while(DeltaBeta < -PI) DeltaBeta += 2*PI;
+
+ if(fabs(DeltaBeta) > DEGTORAD(20.0f) && MovingForward && TheCamera.m_uiTransitionState == 0)
+ m_bFixingBeta = true;
+
+ CPad *pad = CPad::GetPad(0);
+ if(!(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight()))
+ if(DirectionWasLooking != LOOKING_FORWARD)
+ TheCamera.m_bCamDirectlyBehind = true;
+
+ if(!m_bFixingBeta && !TheCamera.m_bUseTransitionBeta && !TheCamera.m_bCamDirectlyBehind && !TheCamera.m_bCamDirectlyInFront)
+ return false;
+
+ bool SetBeta = false;
+ if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || TheCamera.m_bUseTransitionBeta)
+ if(&TheCamera.Cams[TheCamera.ActiveCam] == this)
+ SetBeta = true;
+
+ if(m_bFixingBeta || SetBeta){
+ WellBufferMe(TargetOrientation, &Beta, &BetaSpeed, 0.15f, 0.007f, true);
+
+ if(TheCamera.m_bCamDirectlyBehind && &TheCamera.Cams[TheCamera.ActiveCam] == this)
+ Beta = TargetOrientation;
+ if(TheCamera.m_bCamDirectlyInFront && &TheCamera.Cams[TheCamera.ActiveCam] == this)
+ Beta = TargetOrientation + PI;
+ if(TheCamera.m_bUseTransitionBeta && &TheCamera.Cams[TheCamera.ActiveCam] == this)
+ Beta = m_fTransitionBeta;
+
+ Source.x = TargetCoors.x - cos(Beta)*Dist;
+ Source.y = TargetCoors.y - sin(Beta)*Dist;
+
+ // Check if we're done
+ DeltaBeta = TargetOrientation - Beta;
+ while(DeltaBeta >= PI) DeltaBeta -= 2*PI;
+ while(DeltaBeta < -PI) DeltaBeta += 2*PI;
+ if(fabs(DeltaBeta) < DEGTORAD(2.0f))
+ m_bFixingBeta = false;
+ }
+ TheCamera.m_bCamDirectlyBehind = false;
+ TheCamera.m_bCamDirectlyInFront = false;
+ return true;
+}
+
+// Move the cam to avoid clipping through buildings
+bool
+CCam::FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation)
+{
+ CVector Target = TargetCoors;
+ bool UseEntityPos = false;
+ CVector EntityPos;
+ static CColPoint colPoint;
+ static bool LastObscured = false;
+
+ if(Mode == MODE_BEHINDCAR)
+ Target.z += TargetHeight/2.0f;
+ if(Mode == MODE_CAMONASTRING){
+ UseEntityPos = true;
+ Target.z += TargetHeight/2.0f;
+ EntityPos = CamTargetEntity->GetPosition();
+ }
+
+ CVector TempSource = Source;
+
+ bool Obscured1 = false;
+ bool Obscured2 = false;
+ bool Fix1 = false;
+ float Dist1 = 0.0f;
+ float Dist2 = 0.0f;
+ CEntity *ent;
+ if(m_bCollisionChecksOn || LastObscured){
+ Obscured1 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true);
+ if(Obscured1){
+ Dist1 = (Target - colPoint.point).Magnitude2D();
+ Fix1 = true;
+ if(UseEntityPos)
+ Obscured1 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true);
+ }else if(m_bFixingBeta){
+ float d = (TempSource - Target).Magnitude();
+ TempSource.x = Target.x - d*cos(TargetOrientation);
+ TempSource.y = Target.y - d*sin(TargetOrientation);
+
+ // same check again
+ Obscured2 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true);
+ if(Obscured2){
+ Dist2 = (Target - colPoint.point).Magnitude2D();
+ if(UseEntityPos)
+ Obscured2 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true);
+ }
+ }
+ LastObscured = Obscured1 || Obscured2;
+ }
+
+ // nothing to do
+ if(!LastObscured)
+ return false;
+
+ if(Fix1){
+ Source.x = Target.x - cos(Beta)*Dist1;
+ Source.y = Target.y - sin(Beta)*Dist1;
+ if(Mode == MODE_BEHINDCAR)
+ Source = colPoint.point;
+ }else{
+ WellBufferMe(Dist2, &m_fDistanceBeforeChanges, &DistanceSpeed, 0.2f, 0.025f, false);
+ Source.x = Target.x - cos(Beta)*m_fDistanceBeforeChanges;
+ Source.y = Target.y - sin(Beta)*m_fDistanceBeforeChanges;
+ }
+
+ if(ResetStatics){
+ m_fDistanceBeforeChanges = (Source - Target).Magnitude2D();
+ DistanceSpeed = 0.0f;
+ Source.x = colPoint.point.x;
+ Source.y = colPoint.point.y;
+ }
+ return true;
+}
+
+void
+CCam::Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float)
+{
+ if(!CamTargetEntity->IsVehicle())
+ return;
+
+ FOV = DefaultFOV;
+
+ if(ResetStatics){
+ AlphaSpeed = 0.0f;
+ if(TheCamera.m_bIdleOn)
+ TheCamera.m_uiTimeWeEnteredIdle = CTimer::GetTimeInMilliseconds();
+ }
+
+ CBaseModelInfo *mi = CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex());
+ CVector Dimensions = mi->GetColModel()->boundingBox.max - mi->GetColModel()->boundingBox.min;
+ float BaseDist = Dimensions.Magnitude2D();
+
+ CVector TargetCoors = CameraTarget;
+ TargetCoors.z += Dimensions.z - 0.1f; // final
+ Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y);
+ while(Alpha >= PI) Alpha -= 2*PI;
+ while(Alpha < -PI) Alpha += 2*PI;
+ while(Beta >= PI) Beta -= 2*PI;
+ while(Beta < -PI) Beta += 2*PI;
+
+ m_fDistanceBeforeChanges = (Source - TargetCoors).Magnitude2D();
+
+ Cam_On_A_String_Unobscured(TargetCoors, BaseDist);
+ WorkOutCamHeight(TargetCoors, TargetOrientation, Dimensions.z);
+ RotCamIfInFrontCar(TargetCoors, TargetOrientation);
+ FixCamIfObscured(TargetCoors, Dimensions.z, TargetOrientation);
+ FixCamWhenObscuredByVehicle(TargetCoors);
+
+ m_cvecTargetCoorsForFudgeInter = TargetCoors;
+ Front = TargetCoors - Source;
+ Front.Normalise();
+ GetVectorsReadyForRW();
+ ResetStatics = false;
+}
+
+// Basic Cam on a string algorithm
+void
+CCam::Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist)
+{
+ CA_MAX_DISTANCE = BaseDist + 0.1f + TheCamera.CarZoomValueSmooth;
+ CA_MIN_DISTANCE = min(BaseDist*0.6f, 3.5f);
+
+ CVector Dist = Source - TargetCoors;
+
+ if(ResetStatics)
+ Source = TargetCoors + Dist*(CA_MAX_DISTANCE + 1.0f);
+
+ float Length = Dist.Magnitude2D();
+ if(Length < 0.001f){
+ // This probably shouldn't happen. reset view
+ CVector Forward = CamTargetEntity->GetForward();
+ Forward.z = 0.0f;
+ Forward.Normalise();
+ Source = TargetCoors - Forward*CA_MAX_DISTANCE;
+ Dist = Source - TargetCoors;
+ Length = Dist.Magnitude2D();
+ }
+
+ if(Length > CA_MAX_DISTANCE){
+ Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE;
+ Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE;
+ }else if(Length < CA_MIN_DISTANCE){
+ Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE;
+ Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE;
+ }
+}
+
+void
+CCam::FixCamWhenObscuredByVehicle(const CVector &TargetCoors)
+{
+ // BUG? is this never reset
+ static float HeightFixerCarsObscuring = 0.0f;
+ static float HeightFixerCarsObscuringSpeed = 0.0f;
+ CColPoint colPoint;
+ CEntity *entity;
+
+ float HeightTarget = 0.0f;
+ if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, false, true, false, false, false, false, false)){
+ CBaseModelInfo *mi = CModelInfo::GetModelInfo(entity->GetModelIndex());
+ HeightTarget = mi->GetColModel()->boundingBox.max.z + 1.0f + TargetCoors.z - Source.z;
+ if(HeightTarget < 0.0f)
+ HeightTarget = 0.0f;
+ }
+ WellBufferMe(HeightTarget, &HeightFixerCarsObscuring, &HeightFixerCarsObscuringSpeed, 0.2f, 0.025f, false);
+ Source.z += HeightFixerCarsObscuring;
+}
+
+bool
+CCam::Using3rdPersonMouseCam()
+{
+ return CCamera::m_bUseMouse3rdPerson &&
+ (Mode == MODE_FOLLOWPED ||
+ TheCamera.m_bPlayerIsInGarage &&
+ FindPlayerPed() && FindPlayerPed()->m_nPedState != PED_DRIVING &&
+ Mode != MODE_TOPDOWN1 && this->CamTargetEntity == FindPlayerPed());
+}
+
+bool
+CCam::GetWeaponFirstPersonOn()
+{
+ CEntity *target = this->CamTargetEntity;
+ if (target && target->IsPed())
+ return ((CPed*)target)->GetWeapon()->m_bAddRotOffset;
+
+ return false;
+}
+
+STARTPATCHES
+ InjectHook(0x42C760, &CCamera::IsSphereVisible, PATCH_JUMP);
+ InjectHook(0x46FD00, &CCamera::SetFadeColour, PATCH_JUMP);
+
+ InjectHook(0x46FD40, &CCamera::SetMotionBlur, PATCH_JUMP);
+ InjectHook(0x46FD80, &CCamera::SetMotionBlurAlpha, PATCH_JUMP);
+ InjectHook(0x46F940, &CCamera::RenderMotionBlur, PATCH_JUMP);
+
+ InjectHook(0x456F40, WellBufferMe, PATCH_JUMP);
+ InjectHook(0x4582F0, &CCam::GetVectorsReadyForRW, PATCH_JUMP);
+ InjectHook(0x457710, &CCam::DoAverageOnVector, PATCH_JUMP);
+ InjectHook(0x458060, &CCam::GetPedBetaAngleForClearView, PATCH_JUMP);
+ InjectHook(0x457210, &CCam::Cam_On_A_String_Unobscured, PATCH_JUMP);
+ InjectHook(0x457A80, &CCam::FixCamWhenObscuredByVehicle, PATCH_JUMP);
+ InjectHook(0x457B90, &CCam::FixCamIfObscured, PATCH_JUMP);
+ InjectHook(0x465DA0, &CCam::RotCamIfInFrontCar, PATCH_JUMP);
+ InjectHook(0x4662D0, &CCam::WorkOutCamHeightWeeCar, PATCH_JUMP);
+ InjectHook(0x466650, &CCam::WorkOutCamHeight, PATCH_JUMP);
+
+ InjectHook(0x45E3A0, &CCam::Process_FollowPed, PATCH_JUMP);
+ InjectHook(0x45BE60, &CCam::Process_BehindCar, PATCH_JUMP);
+ InjectHook(0x45C090, &CCam::Process_Cam_On_A_String, PATCH_JUMP);
+
+ InjectHook(0x473250, &CCamera::dtor, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Camera.h b/src/core/Camera.h
new file mode 100644
index 00000000..db5fff46
--- /dev/null
+++ b/src/core/Camera.h
@@ -0,0 +1,477 @@
+#pragma once
+#include "Placeable.h"
+
+class CEntity;
+class CPed;
+class CAutomobile;
+
+#define NUMBER_OF_VECTORS_FOR_AVERAGE 2
+
+struct CCam
+{
+ enum
+ {
+ MODE_TOPDOWN1 = 1,
+ MODE_TOPDOWN2,
+ MODE_BEHINDCAR,
+ MODE_FOLLOWPED,
+ MODE_AIMING,
+ MODE_DEBUG,
+ MODE_SNIPER,
+ MODE_ROCKET,
+ MODE_MODELVIEW,
+ MODE_BILL,
+ MODE_SYPHON,
+ MODE_CIRCLE,
+ MODE_CHEESYZOOM,
+ MODE_WHEELCAM,
+ MODE_FIXED,
+ MODE_FIRSTPERSON,
+ MODE_FLYBY,
+ MODE_CAMONASTRING,
+ MODE_REACTIONCAM,
+ MODE_FOLLOWPEDWITHBINDING,
+ MODE_CHRISWITHBINDINGPLUSROTATION,
+ MODE_BEHINDBOAT,
+ MODE_PLAYERFALLENWATER,
+ MODE_CAMONTRAINROOF,
+ MODE_CAMRUNNINGSIDETRAIN,
+ MODE_BLOODONTHETRACKS,
+ MODE_IMTHEPASSENGERWOOWOO,
+ MODE_SYPHONCRIMINFRONT,
+ MODE_PEDSDEADBABY,
+ MODE_CUSHYPILLOWSARSE,
+ MODE_LOOKATCARS,
+ MODE_ARRESTCAMONE,
+ MODE_ARRESTCAMTWO,
+ MODE_M16FIRSTPERSON_34,
+ MODE_SPECIALFIXEDFORSYPHON,
+ MODE_FIGHT,
+ MODE_TOPDOWNPED,
+ MODE_SNIPER_RUN_AROUND,
+ MODE_ROCKET_RUN_AROUND,
+ MODE_FIRSTPERSONPEDONPC_40,
+ MODE_FIRSTPERSONPEDONPC_41,
+ MODE_FIRSTPERSONPEDONPC_42,
+ MODE_EDITOR,
+ MODE_M16FIRSTPERSON_44
+ };
+
+ bool bBelowMinDist; //used for follow ped mode
+ bool bBehindPlayerDesired; //used for follow ped mode
+ bool m_bCamLookingAtVector;
+ bool m_bCollisionChecksOn;
+ bool m_bFixingBeta; //used for camera on a string
+ bool m_bTheHeightFixerVehicleIsATrain;
+ bool LookBehindCamWasInFront;
+ bool LookingBehind;
+ bool LookingLeft; // 32
+ bool LookingRight;
+ bool ResetStatics; //for interpolation type stuff to work
+ bool Rotating;
+
+ int16 Mode; // CameraMode
+ uint32 m_uiFinishTime; // 52
+
+ int m_iDoCollisionChecksOnFrameNum;
+ int m_iDoCollisionCheckEveryNumOfFrames;
+ int m_iFrameNumWereAt; // 64
+ int m_iRunningVectorArrayPos;
+ int m_iRunningVectorCounter;
+ int DirectionWasLooking;
+
+ float f_max_role_angle; //=DEGTORAD(5.0f);
+ float f_Roll; //used for adding a slight roll to the camera in the
+ float f_rollSpeed;
+ float m_fSyphonModeTargetZOffSet;
+ float m_fUnknownZOffSet;
+ float m_fAmountFractionObscured;
+ float m_fAlphaSpeedOverOneFrame; // 100
+ float m_fBetaSpeedOverOneFrame;
+ float m_fBufferedTargetBeta;
+ float m_fBufferedTargetOrientation;
+ float m_fBufferedTargetOrientationSpeed;
+ float m_fCamBufferedHeight;
+ float m_fCamBufferedHeightSpeed;
+ float m_fCloseInPedHeightOffset;
+ float m_fCloseInPedHeightOffsetSpeed; // 132
+ float m_fCloseInCarHeightOffset;
+ float m_fCloseInCarHeightOffsetSpeed;
+ float m_fDimensionOfHighestNearCar;
+ float m_fDistanceBeforeChanges;
+ float m_fFovSpeedOverOneFrame;
+ float m_fMinDistAwayFromCamWhenInterPolating;
+ float m_fPedBetweenCameraHeightOffset;
+ float m_fPlayerInFrontSyphonAngleOffSet; // 164
+ float m_fRadiusForDead;
+ float m_fRealGroundDist; //used for follow ped mode
+ float m_fTargetBeta;
+ float m_fTimeElapsedFloat;
+
+ float m_fTransitionBeta;
+ float m_fTrueBeta;
+ float m_fTrueAlpha; // 200
+ float m_fInitialPlayerOrientation; //used for first person
+
+ float Alpha;
+ float AlphaSpeed;
+ float FOV;
+ float FOVSpeed;
+ float Beta;
+ float BetaSpeed;
+ float Distance; // 232
+ float DistanceSpeed;
+ float CA_MIN_DISTANCE;
+ float CA_MAX_DISTANCE;
+ float SpeedVar;
+
+ // ped onfoot zoom distance
+ float m_fTargetZoomGroundOne;
+ float m_fTargetZoomGroundTwo; // 256
+ float m_fTargetZoomGroundThree;
+ // ped onfoot alpha angle offset
+ float m_fTargetZoomOneZExtra;
+ float m_fTargetZoomTwoZExtra;
+ float m_fTargetZoomThreeZExtra;
+
+ float m_fTargetZoomZCloseIn;
+ float m_fMinRealGroundDist;
+ float m_fTargetCloseInDist;
+
+ CVector m_cvecTargetCoorsForFudgeInter; // 360
+ CVector m_cvecCamFixedModeVector; // 372
+ CVector m_cvecCamFixedModeSource; // 384
+ CVector m_cvecCamFixedModeUpOffSet; // 396
+ CVector m_vecLastAboveWaterCamPosition; //408 //helper for when the player has gone under the water
+ CVector m_vecBufferedPlayerBodyOffset; // 420
+
+ // The three vectors that determine this camera for this frame
+ CVector Front; // 432 // Direction of looking in
+ CVector Source; // Coors in world space
+ CVector SourceBeforeLookBehind;
+ CVector Up; // Just that
+ CVector m_arrPreviousVectors[NUMBER_OF_VECTORS_FOR_AVERAGE]; // used to average stuff
+ CEntity *CamTargetEntity;
+
+ float m_fCameraDistance;
+ float m_fIdealAlpha;
+ float m_fPlayerVelocity;
+ CAutomobile *m_pLastCarEntered; // So interpolation works
+ CPed *m_pLastPedLookedAt;// So interpolation works
+ bool m_bFirstPersonRunAboutActive;
+
+
+ void GetVectorsReadyForRW(void);
+ CVector DoAverageOnVector(const CVector &vec);
+ float GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies);
+ void WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation);
+ void WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight);
+ bool RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation);
+ bool FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation);
+ void Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist);
+ void FixCamWhenObscuredByVehicle(const CVector &TargetCoors);
+ bool Using3rdPersonMouseCam();
+ bool GetWeaponFirstPersonOn();
+
+ void Process_Debug(float *vec, float a, float b, float c);
+ void Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float);
+ void Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float);
+ void Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float);
+};
+static_assert(sizeof(CCam) == 0x1A4, "CCam: wrong size");
+static_assert(offsetof(CCam, Alpha) == 0xA8, "CCam: error");
+static_assert(offsetof(CCam, Front) == 0x140, "CCam: error");
+
+struct CCamPathSplines
+{
+ float m_arr_PathData[800];
+};
+
+struct CTrainCamNode
+{
+ CVector m_cvecCamPosition;
+ CVector m_cvecPointToLookAt;
+ CVector m_cvecMinPointInRange;
+ CVector m_cvecMaxPointInRange;
+ float m_fDesiredFOV;
+ float m_fNearClip;
+};
+
+struct CQueuedMode
+{
+ int16 Mode;
+ float Duration;
+ int16 MinZoom;
+ int16 MaxZoom;
+};
+
+enum
+{
+ LOOKING_BEHIND,
+ LOOKING_LEFT,
+ LOOKING_RIGHT,
+ LOOKING_FORWARD,
+};
+
+enum
+{
+ // TODO: figure out
+ FADE_0,
+ FADE_1, // mid fade
+ FADE_2,
+
+ FADE_OUT = 0,
+ FADE_IN,
+};
+
+enum
+{
+ MBLUR_NONE,
+ MBLUR_SNIPER,
+ MBLUR_NORMAL,
+ MBLUR_INTRO1, // green camera
+ MBLUR_INTRO2, // unused
+ MBLUR_INTRO3, // bank scene
+ MBLUR_INTRO4, // jail break scene
+ MBLUR_INTRO5, // explosion
+ MBLUR_INTRO6, // player shot
+ MBLUR_UNUSED, // pinkish
+};
+
+struct CCamera : public CPlaceable
+{
+ bool m_bAboveGroundTrainNodesLoaded;
+ bool m_bBelowGroundTrainNodesLoaded;
+ bool m_bCamDirectlyBehind;
+ bool m_bCamDirectlyInFront;
+ bool m_bCameraJustRestored;
+ bool m_bcutsceneFinished;
+ bool m_bCullZoneChecksOn;
+ bool m_bFirstPersonBeingUsed;
+ bool m_bJustJumpedOutOf1stPersonBecauseOfTarget;
+ bool m_bIdleOn;
+ bool m_bInATunnelAndABigVehicle;
+ bool m_bInitialNodeFound;
+ bool m_bInitialNoNodeStaticsSet;
+ bool m_bIgnoreFadingStuffForMusic;
+ bool m_bPlayerIsInGarage;
+ bool m_bJustCameOutOfGarage;
+ bool m_bJustInitalised;
+ bool m_bJust_Switched;
+ bool m_bLookingAtPlayer;
+ bool m_bLookingAtVector;
+ bool m_bMoveCamToAvoidGeom;
+ bool m_bObbeCinematicPedCamOn;
+ bool m_bObbeCinematicCarCamOn;
+ bool m_bRestoreByJumpCut;
+ bool m_bUseNearClipScript;
+ bool m_bStartInterScript;
+ bool m_bStartingSpline;
+ bool m_bTargetJustBeenOnTrain;
+ bool m_bTargetJustCameOffTrain;
+ bool m_bUseSpecialFovTrain;
+ bool m_bUseTransitionBeta;
+ bool m_bUseScriptZoomValuePed;
+ bool m_bUseScriptZoomValueCar;
+ bool m_bWaitForInterpolToFinish;
+ bool m_bItsOkToLookJustAtThePlayer;
+ bool m_bWantsToSwitchWidescreenOff;
+ bool m_WideScreenOn;
+ bool m_1rstPersonRunCloseToAWall;
+ bool m_bHeadBob;
+ bool m_bFailedCullZoneTestPreviously;
+
+bool m_FadeTargetIsSplashScreen;
+
+ bool WorldViewerBeingUsed;
+ uint8 ActiveCam;
+ uint32 m_uiCamShakeStart;
+ uint32 m_uiFirstPersonCamLastInputTime;
+// where are those?
+//bool m_bVehicleSuspenHigh;
+//bool m_bEnable1rstPersonCamCntrlsScript;
+//bool m_bAllow1rstPersonWeaponsCamera;
+
+ uint32 m_uiLongestTimeInMill;
+ uint32 m_uiNumberOfTrainCamNodes;
+ uint8 m_uiTransitionJUSTStarted;
+ uint8 m_uiTransitionState; // 0:one mode 1:transition
+
+ uint32 m_uiTimeLastChange;
+ uint32 m_uiTimeWeEnteredIdle;
+ uint32 m_uiTimeTransitionStart;
+ uint32 m_uiTransitionDuration;
+ int m_BlurBlue;
+ int m_BlurGreen;
+ int m_BlurRed;
+ int m_BlurType;
+
+uint32 unknown;
+ int m_iWorkOutSpeedThisNumFrames;
+ int m_iNumFramesSoFar;
+
+
+ int m_iCurrentTrainCamNode;
+ int m_motionBlur;
+ int m_imotionBlurAddAlpha;
+ int m_iCheckCullZoneThisNumFrames;
+ int m_iZoneCullFrameNumWereAt;
+ int WhoIsInControlOfTheCamera;
+
+ float CamFrontXNorm;
+ float CamFrontYNorm;
+ float CarZoomIndicator;
+ float CarZoomValue;
+ float CarZoomValueSmooth;
+
+ float DistanceToWater;
+ float FOVDuringInter;
+ float LODDistMultiplier;
+ float GenerationDistMultiplier;
+ float m_fAlphaSpeedAtStartInter;
+ float m_fAlphaWhenInterPol;
+ float m_fAlphaDuringInterPol;
+ float m_fBetaDuringInterPol;
+ float m_fBetaSpeedAtStartInter;
+ float m_fBetaWhenInterPol;
+ float m_fFOVWhenInterPol;
+ float m_fFOVSpeedAtStartInter;
+ float m_fStartingBetaForInterPol;
+ float m_fStartingAlphaForInterPol;
+ float m_PedOrientForBehindOrInFront;
+ float m_CameraAverageSpeed;
+ float m_CameraSpeedSoFar;
+ float m_fCamShakeForce;
+ float m_fCarZoomValueScript;
+ float m_fFovForTrain;
+ float m_fFOV_Wide_Screen;
+ float m_fNearClipScript;
+ float m_fOldBetaDiff;
+ float m_fPedZoomValue;
+
+ float m_fPedZoomValueScript;
+ float m_fPedZoomValueSmooth;
+ float m_fPositionAlongSpline;
+ float m_ScreenReductionPercentage;
+ float m_ScreenReductionSpeed;
+ float m_AlphaForPlayerAnim1rstPerson;
+ float Orientation;
+ float PedZoomIndicator;
+ float PlayerExhaustion;
+ float SoundDistUp, SoundDistLeft, SoundDistRight;
+ float SoundDistUpAsRead, SoundDistLeftAsRead, SoundDistRightAsRead;
+ float SoundDistUpAsReadOld, SoundDistLeftAsReadOld, SoundDistRightAsReadOld;
+ float m_fWideScreenReductionAmount;
+ float m_fStartingFOVForInterPol;
+
+ // not static yet
+ float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls
+ float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls
+ float m_f3rdPersonCHairMultX;
+ float m_f3rdPersonCHairMultY;
+
+
+ CCam Cams[3];
+ void *pToGarageWeAreIn;
+ void *pToGarageWeAreInForHackAvoidFirstPerson;
+ CQueuedMode m_PlayerMode;
+ CQueuedMode PlayerWeaponMode;
+ CVector m_PreviousCameraPosition;
+ CVector m_RealPreviousCameraPosition;
+ CVector m_cvecAimingTargetCoors;
+ CVector m_vecFixedModeVector;
+
+ // one of those has to go
+ CVector m_vecFixedModeSource;
+ CVector m_vecFixedModeUpOffSet;
+// CVector m_vecCutSceneOffset;
+ CVector m_cvecStartingSourceForInterPol;
+ CVector m_cvecStartingTargetForInterPol;
+ CVector m_cvecStartingUpForInterPol;
+ CVector m_cvecSourceSpeedAtStartInter;
+ CVector m_cvecTargetSpeedAtStartInter;
+ CVector m_cvecUpSpeedAtStartInter;
+ CVector m_vecSourceWhenInterPol;
+ CVector m_vecTargetWhenInterPol;
+ CVector m_vecUpWhenInterPol;
+ CVector m_vecClearGeometryVec;
+
+ CVector m_vecGameCamPos;
+ CVector SourceDuringInter;
+ CVector TargetDuringInter;
+ CVector UpDuringInter;
+ RwCamera *m_pRwCamera;
+ CEntity *pTargetEntity;
+ CCamPathSplines m_arrPathArray[4];
+ CTrainCamNode m_arrTrainCamNode[800];
+ CMatrix m_cameraMatrix;
+ bool m_bGarageFixedCamPositionSet;
+ bool m_vecDoingSpecialInterPolation;
+ bool m_bScriptParametersSetForInterPol;
+ bool m_bFading;
+ bool m_bMusicFading;
+ CMatrix m_viewMatrix;
+ CVector m_vecFrustumNormals[4];
+ CVector m_vecOldSourceForInter;
+ CVector m_vecOldFrontForInter;
+ CVector m_vecOldUpForInter;
+
+ float m_vecOldFOVForInter;
+ float m_fFLOATingFade;
+ float m_fFLOATingFadeMusic;
+ float m_fTimeToFadeOut;
+ float m_fTimeToFadeMusic;
+ float m_fFractionInterToStopMovingTarget;
+ float m_fFractionInterToStopCatchUpTarget;
+ float m_fGaitSwayBuffer;
+ float m_fScriptPercentageInterToStopMoving;
+ float m_fScriptPercentageInterToCatchUp;
+
+uint32 m_fScriptTimeForInterPolation;
+
+
+int16 m_iFadingDirection;
+int m_iModeObbeCamIsInForCar;
+ int16 m_iModeToGoTo;
+ int16 m_iMusicFadingDirection;
+ int16 m_iTypeOfSwitch;
+
+ uint32 m_uiFadeTimeStarted;
+ uint32 m_uiFadeTimeStartedMusic;
+
+ static bool &m_bUseMouse3rdPerson;
+
+ CMatrix &GetCameraMatrix(void) { return m_cameraMatrix; }
+ CVector &GetGameCamPosition(void) { return m_vecGameCamPos; }
+ bool IsPointVisible(const CVector &center, const CMatrix *mat);
+ bool IsSphereVisible(const CVector &center, float radius, const CMatrix *mat);
+ bool IsBoxVisible(RwV3d *box, const CMatrix *mat);
+ int GetLookDirection(void);
+
+ void Fade(float timeout, int16 direction);
+ int GetScreenFadeStatus(void);
+ void ProcessFade(void);
+ void ProcessMusicFade(void);
+ void SetFadeColour(uint8 r, uint8 g, uint8 b);
+
+ void SetMotionBlur(int r, int g, int b, int a, int type);
+ void SetMotionBlurAlpha(int a);
+ void RenderMotionBlur(void);
+ void ClearPlayerWeaponMode();
+ void CalculateDerivedValues(void);
+
+ void DrawBordersForWideScreen(void);
+ void Restore(void);
+ void SetWidescreenOff(void);
+
+ void dtor(void) { this->CCamera::~CCamera(); }
+};
+static_assert(offsetof(CCamera, m_WideScreenOn) == 0x70, "CCamera: error");
+static_assert(offsetof(CCamera, WorldViewerBeingUsed) == 0x75, "CCamera: error");
+static_assert(offsetof(CCamera, m_uiNumberOfTrainCamNodes) == 0x84, "CCamera: error");
+static_assert(offsetof(CCamera, m_uiTransitionState) == 0x89, "CCamera: error");
+static_assert(offsetof(CCamera, m_uiTimeTransitionStart) == 0x94, "CCamera: error");
+static_assert(offsetof(CCamera, m_BlurBlue) == 0x9C, "CCamera: error");
+static_assert(offsetof(CCamera, Cams) == 0x1A4, "CCamera: error");
+static_assert(sizeof(CCamera) == 0xE9D8, "CCamera: wrong size");
+extern CCamera &TheCamera;
diff --git a/src/core/CdStream.cpp b/src/core/CdStream.cpp
new file mode 100644
index 00000000..255e46bb
--- /dev/null
+++ b/src/core/CdStream.cpp
@@ -0,0 +1,529 @@
+#include <windows.h>
+#include "common.h"
+#include "patcher.h"
+#include "CdStream.h"
+#include "rwcore.h"
+#include "RwHelper.h"
+
+#define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", __VA_ARGS__)
+#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", __VA_ARGS__)
+
+struct CdReadInfo
+{
+ uint32 nSectorOffset;
+ uint32 nSectorsToRead;
+ void *pBuffer;
+ char field_C;
+ bool bLocked;
+ bool bInUse;
+ char _pad0;
+ int32 nStatus;
+ HANDLE hSemaphore;
+ HANDLE hFile;
+ OVERLAPPED Overlapped;
+};
+VALIDATE_SIZE(CdReadInfo, 0x30);
+
+char gCdImageNames[MAX_CDIMAGES+1][64];
+int32 gNumImages;
+int32 gNumChannels;
+
+HANDLE gImgFiles[MAX_CDIMAGES];
+
+HANDLE _gCdStreamThread;
+HANDLE gCdStreamSema;
+DWORD _gCdStreamThreadId;
+
+CdReadInfo *gpReadInfo;
+Queue gChannelRequestQ;
+
+int32 lastPosnRead;
+
+BOOL _gbCdStreamOverlapped;
+BOOL _gbCdStreamAsync;
+DWORD _gdwCdStreamFlags;
+
+
+void
+CdStreamInitThread(void)
+{
+ SetLastError(0);
+
+ if ( gNumChannels > 0 )
+ {
+ for ( int32 i = 0; i < gNumChannels; i++ )
+ {
+ gpReadInfo[i].hSemaphore = CreateSemaphore(nil, 0, 2, nil);
+
+ if ( gpReadInfo[i].hSemaphore == nil )
+ {
+ CDTRACE("failed to create sync semaphore");
+ ASSERT(0);
+ return;
+ }
+ }
+ }
+
+ gChannelRequestQ.items = (int32 *)LocalAlloc(LMEM_ZEROINIT, sizeof(int32) * (gNumChannels + 1));
+ gChannelRequestQ.head = 0;
+ gChannelRequestQ.tail = 0;
+ gChannelRequestQ.size = gNumChannels + 1;
+ ASSERT(gChannelRequestQ.items != nil );
+
+ gCdStreamSema = CreateSemaphore(nil, 0, 5, "CdStream");
+
+ if ( gCdStreamSema == nil )
+ {
+ CDTRACE("failed to create stream semaphore");
+ ASSERT(0);
+ return;
+ }
+
+ _gCdStreamThread = CreateThread(nil, 64*1024/*64KB*/, CdStreamThread, nil, CREATE_SUSPENDED, &_gCdStreamThreadId);
+
+ if ( _gCdStreamThread == nil )
+ {
+ CDTRACE("failed to create streaming thread");
+ ASSERT(0);
+ return;
+ }
+
+ SetThreadPriority(_gCdStreamThread, GetThreadPriority(GetCurrentThread()) - 1);
+
+ ResumeThread(_gCdStreamThread);
+}
+
+void
+CdStreamInit(int32 numChannels)
+{
+ DWORD SectorsPerCluster;
+ DWORD BytesPerSector;
+ DWORD NumberOfFreeClusters;
+ DWORD TotalNumberOfClusters;
+
+ GetDiskFreeSpace(nil, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters);
+
+ _gdwCdStreamFlags = 0;
+
+ if ( BytesPerSector <= CDSTREAM_SECTOR_SIZE )
+ {
+ _gdwCdStreamFlags |= FILE_FLAG_NO_BUFFERING;
+ debug("Using no buffered loading for streaming\n");
+ }
+
+ _gbCdStreamOverlapped = TRUE;
+
+ _gdwCdStreamFlags |= FILE_FLAG_OVERLAPPED;
+
+ _gbCdStreamAsync = FALSE;
+
+ void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, BytesPerSector);
+ ASSERT( pBuffer != nil );
+
+ SetLastError(0);
+
+ gNumImages = 0;
+
+ gNumChannels = numChannels;
+
+ gpReadInfo = (CdReadInfo *)LocalAlloc(LMEM_ZEROINIT, sizeof(CdReadInfo) * numChannels);
+ ASSERT( gpReadInfo != nil );
+
+ CDDEBUG("read info %p", gpReadInfo);
+
+ CdStreamAddImage("MODELS\\GTA3.IMG");
+
+ int32 nStatus = CdStreamRead(0, pBuffer, 0, 1);
+
+ CdStreamRemoveImages();
+
+ if ( nStatus == STREAM_SUCCESS )
+ {
+ _gbCdStreamAsync = TRUE;
+
+ debug("Using async loading for streaming\n");
+ }
+ else
+ {
+ _gdwCdStreamFlags &= ~FILE_FLAG_OVERLAPPED;
+
+ _gbCdStreamOverlapped = FALSE;
+
+ _gbCdStreamAsync = TRUE;
+
+ debug("Using sync loading for streaming\n");
+ }
+
+ CdStreamInitThread();
+
+ ASSERT( pBuffer != nil );
+ RwFreeAlign(pBuffer);
+}
+
+uint32
+GetGTA3ImgSize(void)
+{
+ ASSERT( gImgFiles[0] != nil );
+ return (uint32)GetFileSize(gImgFiles[0], nil);
+}
+
+void
+CdStreamShutdown(void)
+{
+ if ( _gbCdStreamAsync )
+ {
+ LocalFree(gChannelRequestQ.items);
+ CloseHandle(gCdStreamSema);
+ CloseHandle(_gCdStreamThread);
+
+ for ( int32 i = 0; i < gNumChannels; i++ )
+ CloseHandle(gpReadInfo[i].hSemaphore);
+ }
+
+ LocalFree(gpReadInfo);
+}
+
+
+
+int32
+CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size)
+{
+ ASSERT( channel < gNumChannels );
+ ASSERT( buffer != nil );
+
+ lastPosnRead = size + offset;
+
+ ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES );
+ HANDLE hImage = gImgFiles[_GET_INDEX(offset)];
+ ASSERT( hImage != nil );
+
+
+ CdReadInfo *pChannel = &gpReadInfo[channel];
+ ASSERT( pChannel != nil );
+
+ pChannel->hFile = hImage;
+
+ SetLastError(0);
+
+ if ( _gbCdStreamAsync )
+ {
+ if ( pChannel->nSectorsToRead != 0 || pChannel->bInUse )
+ return STREAM_NONE;
+
+ pChannel->nStatus = STREAM_NONE;
+ pChannel->nSectorOffset = _GET_OFFSET(offset);
+ pChannel->nSectorsToRead = size;
+ pChannel->pBuffer = buffer;
+ pChannel->bLocked = 0;
+
+ AddToQueue(&gChannelRequestQ, channel);
+
+ if ( !ReleaseSemaphore(gCdStreamSema, 1, nil) )
+ printf("Signal Sema Error\n");
+
+ return STREAM_SUCCESS;
+ }
+
+ if ( _gbCdStreamOverlapped )
+ {
+ ASSERT( channel < gNumChannels );
+ CdReadInfo *pChannel = &gpReadInfo[channel];
+ ASSERT( pChannel != nil );
+
+ pChannel->Overlapped.Offset = _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE;
+
+ if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, NULL, &pChannel->Overlapped)
+ && GetLastError() != ERROR_IO_PENDING )
+ return STREAM_NONE;
+ else
+ return STREAM_SUCCESS;
+ }
+
+ SetFilePointer(hImage, _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN);
+
+ DWORD NumberOfBytesRead;
+
+ if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, &NumberOfBytesRead, nil) )
+ return STREAM_NONE;
+ else
+ return STREAM_SUCCESS;
+}
+
+int32
+CdStreamGetStatus(int32 channel)
+{
+ ASSERT( channel < gNumChannels );
+ CdReadInfo *pChannel = &gpReadInfo[channel];
+ ASSERT( pChannel != nil );
+
+ if ( _gbCdStreamAsync )
+ {
+ if ( pChannel->bInUse )
+ return STREAM_READING;
+
+ if ( pChannel->nSectorsToRead != 0 )
+ return STREAM_WAITING;
+
+ if ( pChannel->nStatus != STREAM_NONE )
+ {
+ int32 status = pChannel->nStatus;
+
+ pChannel->nStatus = STREAM_NONE;
+
+ return status;
+ }
+
+ return STREAM_NONE;
+ }
+
+ if ( _gbCdStreamOverlapped )
+ {
+ ASSERT( pChannel->hFile != nil );
+ if ( WaitForSingleObjectEx(pChannel->hFile, 0, TRUE) == WAIT_OBJECT_0 )
+ return STREAM_NONE;
+ else
+ return STREAM_READING;
+ }
+
+ return STREAM_NONE;
+}
+
+int32
+CdStreamGetLastPosn(void)
+{
+ return lastPosnRead;
+}
+
+int32
+CdStreamSync(int32 channel)
+{
+ ASSERT( channel < gNumChannels );
+ CdReadInfo *pChannel = &gpReadInfo[channel];
+ ASSERT( pChannel != nil );
+
+ if ( _gbCdStreamAsync )
+ {
+ if ( pChannel->nSectorsToRead != 0 )
+ {
+ pChannel->bLocked = true;
+
+ ASSERT( pChannel->hSemaphore != nil );
+
+ WaitForSingleObject(pChannel->hSemaphore, INFINITE);
+ }
+
+ pChannel->bInUse = false;
+
+ return pChannel->nStatus;
+ }
+
+ DWORD NumberOfBytesTransferred;
+
+ if ( _gbCdStreamOverlapped && pChannel->hFile )
+ {
+ ASSERT(pChannel->hFile != nil );
+ if ( GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) )
+ return STREAM_NONE;
+ else
+ return STREAM_ERROR;
+ }
+
+ return STREAM_NONE;
+}
+
+void
+AddToQueue(Queue *queue, int32 item)
+{
+ ASSERT( queue != nil );
+ ASSERT( queue->items != nil );
+ queue->items[queue->tail] = item;
+
+ queue->tail = (queue->tail + 1) % queue->size;
+
+ if ( queue->head == queue->tail )
+ debug("Queue is full\n");
+}
+
+int32
+GetFirstInQueue(Queue *queue)
+{
+ ASSERT( queue != nil );
+ if ( queue->head == queue->tail )
+ return -1;
+
+ ASSERT( queue->items != nil );
+ return queue->items[queue->head];
+}
+
+void
+RemoveFirstInQueue(Queue *queue)
+{
+ ASSERT( queue != nil );
+ if ( queue->head == queue->tail )
+ {
+ debug("Queue is empty\n");
+ return;
+ }
+
+ queue->head = (queue->head + 1) % queue->size;
+}
+
+DWORD
+WINAPI CdStreamThread(LPVOID lpThreadParameter)
+{
+ debug("Created cdstream thread\n");
+
+ while ( true )
+ {
+ WaitForSingleObject(gCdStreamSema, INFINITE);
+
+ int32 channel = GetFirstInQueue(&gChannelRequestQ);
+ ASSERT( channel < gNumChannels );
+
+ CdReadInfo *pChannel = &gpReadInfo[channel];
+ ASSERT( pChannel != nil );
+
+ pChannel->bInUse = true;
+
+ if ( pChannel->nStatus == STREAM_NONE )
+ {
+ if ( _gbCdStreamOverlapped )
+ {
+ pChannel->Overlapped.Offset = pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE;
+
+ ASSERT(pChannel->hFile != nil );
+ ASSERT(pChannel->pBuffer != nil );
+
+ DWORD NumberOfBytesTransferred;
+
+ if ( ReadFile(pChannel->hFile,
+ pChannel->pBuffer,
+ pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE,
+ NULL,
+ &pChannel->Overlapped) )
+ {
+ pChannel->nStatus = STREAM_NONE;
+ }
+ else if ( GetLastError() == ERROR_IO_PENDING
+ && GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) )
+ {
+ pChannel->nStatus = STREAM_NONE;
+ }
+ else
+ {
+ pChannel->nStatus = STREAM_ERROR;
+ }
+ }
+ else
+ {
+ ASSERT(pChannel->hFile != nil );
+ ASSERT(pChannel->pBuffer != nil );
+
+ SetFilePointer(pChannel->hFile, pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN);
+
+ DWORD NumberOfBytesRead;
+ if ( ReadFile(pChannel->hFile,
+ pChannel->pBuffer,
+ pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE,
+ &NumberOfBytesRead,
+ NULL) )
+ {
+ pChannel->nStatus = STREAM_NONE;
+ }
+ }
+ }
+
+ RemoveFirstInQueue(&gChannelRequestQ);
+
+ pChannel->nSectorsToRead = 0;
+
+ if ( pChannel->bLocked )
+ {
+ ASSERT( pChannel->hSemaphore != nil );
+ ReleaseSemaphore(pChannel->hSemaphore, 1, NULL);
+ }
+
+ pChannel->bInUse = false;
+ }
+}
+
+bool
+CdStreamAddImage(char const *path)
+{
+ ASSERT(path != nil);
+ ASSERT(gNumImages < MAX_CDIMAGES);
+
+ SetLastError(0);
+
+ gImgFiles[gNumImages] = CreateFile(path,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ nil,
+ OPEN_EXISTING,
+ _gdwCdStreamFlags | FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_READONLY,
+ nil);
+
+ ASSERT( gImgFiles[gNumImages] != nil );
+ if ( gImgFiles[gNumImages] == NULL )
+ return false;
+
+ strcpy(gCdImageNames[gNumImages], path);
+
+ gNumImages++;
+
+ return true;
+}
+
+char *
+CdStreamGetImageName(int32 cd)
+{
+ ASSERT(cd < MAX_CDIMAGES);
+ if ( gImgFiles[cd] != nil )
+ return gCdImageNames[cd];
+
+ return nil;
+}
+
+void
+CdStreamRemoveImages(void)
+{
+ for ( int32 i = 0; i < gNumChannels; i++ )
+ CdStreamSync(i);
+
+ for ( int32 i = 0; i < gNumImages; i++ )
+ {
+ SetLastError(0);
+
+ CloseHandle(gImgFiles[i]);
+ gImgFiles[i] = nil;
+ }
+
+ gNumImages = 0;
+}
+
+int32
+CdStreamGetNumImages(void)
+{
+ return gNumImages;
+}
+
+
+STARTPATCHES
+ InjectHook(0x405B50, CdStreamInitThread, PATCH_JUMP);
+ InjectHook(0x405C80, CdStreamInit, PATCH_JUMP);
+ //InjectHook(0x405DB0, debug, PATCH_JUMP);
+ InjectHook(0x405DC0, GetGTA3ImgSize, PATCH_JUMP);
+ InjectHook(0x405DD0, CdStreamShutdown, PATCH_JUMP);
+ InjectHook(0x405E40, CdStreamRead, PATCH_JUMP);
+ InjectHook(0x405F90, CdStreamGetStatus, PATCH_JUMP);
+ InjectHook(0x406000, CdStreamGetLastPosn, PATCH_JUMP);
+ InjectHook(0x406010, CdStreamSync, PATCH_JUMP);
+ InjectHook(0x4060B0, AddToQueue, PATCH_JUMP);
+ InjectHook(0x4060F0, GetFirstInQueue, PATCH_JUMP);
+ InjectHook(0x406110, RemoveFirstInQueue, PATCH_JUMP);
+ InjectHook(0x406140, CdStreamThread, PATCH_JUMP);
+ InjectHook(0x406270, CdStreamAddImage, PATCH_JUMP);
+ InjectHook(0x4062E0, CdStreamGetImageName, PATCH_JUMP);
+ InjectHook(0x406300, CdStreamRemoveImages, PATCH_JUMP);
+ InjectHook(0x406370, CdStreamGetNumImages, PATCH_JUMP);
+ENDPATCHES \ No newline at end of file
diff --git a/src/core/CdStream.h b/src/core/CdStream.h
new file mode 100644
index 00000000..55507aa8
--- /dev/null
+++ b/src/core/CdStream.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#define CDSTREAM_SECTOR_SIZE 2048
+
+#define _GET_INDEX(a) (a >> 24)
+#define _GET_OFFSET(a) (a & 0xFFFFFF)
+
+enum
+{
+ STREAM_NONE = uint8( 0),
+ STREAM_SUCCESS = uint8( 1),
+ STREAM_READING = uint8(-1), // 0xFF,
+ STREAM_ERROR = uint8(-2), // 0xFE,
+ STREAM_ERROR_NOCD = uint8(-3), // 0xFD,
+ STREAM_ERROR_WRONGCD = uint8(-4), // 0xFC,
+ STREAM_ERROR_OPENCD = uint8(-5), // 0xFB,
+ STREAM_WAITING = uint8(-6) // 0xFA,
+};
+
+struct Queue
+{
+ int32 *items;
+ int32 head;
+ int32 tail;
+ int32 size;
+};
+
+VALIDATE_SIZE(Queue, 0x10);
+
+
+void CdStreamInitThread(void);
+void CdStreamInit(int32 numChannels);
+uint32 GetGTA3ImgSize(void);
+void CdStreamShutdown(void);
+int32 CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size);
+int32 CdStreamGetStatus(int32 channel);
+int32 CdStreamGetLastPosn(void);
+int32 CdStreamSync(int32 channel);
+void AddToQueue(Queue *queue, int32 item);
+int32 GetFirstInQueue(Queue *queue);
+void RemoveFirstInQueue(Queue *queue);
+DWORD WINAPI CdStreamThread(LPVOID lpThreadParameter);
+bool CdStreamAddImage(char const *path);
+char *CdStreamGetImageName(int32 cd);
+void CdStreamRemoveImages(void);
+int32 CdStreamGetNumImages(void); \ No newline at end of file
diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp
new file mode 100644
index 00000000..707b0e57
--- /dev/null
+++ b/src/core/Clock.cpp
@@ -0,0 +1,131 @@
+#include "common.h"
+#include "patcher.h"
+#include "Timer.h"
+#include "Pad.h"
+#include "Clock.h"
+#include "Stats.h"
+
+_TODO("gbFastTime");
+bool &gbFastTime = *(bool*)0x95CDBB;
+
+uint8 &CClock::ms_nGameClockHours = *(uint8*)0x95CDA6;
+uint8 &CClock::ms_nGameClockMinutes = *(uint8*)0x95CDC8;
+uint16 &CClock::ms_nGameClockSeconds = *(uint16*)0x95CC7C;
+uint8 &CClock::ms_Stored_nGameClockHours = *(uint8*)0x95CD7B;
+uint8 &CClock::ms_Stored_nGameClockMinutes = *(uint8*)0x95CD9B;
+uint16 &CClock::ms_Stored_nGameClockSeconds = *(uint16*)0x95CC9C;
+uint32 &CClock::ms_nMillisecondsPerGameMinute = *(uint32*)0x8F2C64;
+int32 &CClock::ms_nLastClockTick = *(int32*)0x9430E4;
+bool &CClock::ms_bClockHasBeenStored = *(bool*)0x95CD82;
+
+void
+CClock::Initialise(uint32 scale)
+{
+ debug("Initialising CClock...\n");
+ ms_nGameClockHours = 12;
+ ms_nGameClockMinutes = 0;
+ ms_nGameClockSeconds = 0;
+ ms_nMillisecondsPerGameMinute = scale;
+ ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
+ ms_bClockHasBeenStored = false;
+ debug("CClock ready\n");
+}
+
+void
+CClock::Update(void)
+{
+ if(CPad::GetPad(1)->GetRightShoulder1())
+ {
+ ms_nGameClockMinutes += 8;
+ ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
+
+ if(ms_nGameClockMinutes >= 60)
+ {
+ ms_nGameClockHours++;
+ ms_nGameClockMinutes = 0;
+ if(ms_nGameClockHours >= 24)
+ ms_nGameClockHours = 0;
+ }
+
+ }
+ else if(CTimer::GetTimeInMilliseconds() - ms_nLastClockTick > ms_nMillisecondsPerGameMinute || gbFastTime)
+ {
+ ms_nGameClockMinutes++;
+ ms_nLastClockTick += ms_nMillisecondsPerGameMinute;
+
+ if ( gbFastTime )
+ ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
+
+ if(ms_nGameClockMinutes >= 60)
+ {
+ ms_nGameClockHours++;
+ ms_nGameClockMinutes = 0;
+ if(ms_nGameClockHours >= 24)
+ {
+ CStats::DaysPassed++;
+ ms_nGameClockHours = 0;
+ }
+ }
+ }
+ ms_nGameClockSeconds +=
+ 60
+ * (CTimer::GetTimeInMilliseconds() - ms_nLastClockTick)
+ / ms_nMillisecondsPerGameMinute;
+}
+
+void
+CClock::SetGameClock(uint8 h, uint8 m)
+{
+ ms_nGameClockHours = h;
+ ms_nGameClockMinutes = m;
+ ms_nGameClockSeconds = 0;
+ ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
+}
+
+int32
+CClock::GetGameClockMinutesUntil(uint8 h, uint8 m)
+{
+ int32 now, then;
+ now = ms_nGameClockHours*60 + ms_nGameClockMinutes;
+ then = h*60 + m;
+ if(then < now)
+ then += 24*60;
+ return then-now;
+}
+
+bool
+CClock::GetIsTimeInRange(uint8 h1, uint8 h2)
+{
+ if(h1 > h2)
+ return ms_nGameClockHours >= h1 || ms_nGameClockHours < h2;
+ else
+ return ms_nGameClockHours >= h1 && ms_nGameClockHours < h2;
+}
+
+void
+CClock::StoreClock(void)
+{
+ ms_Stored_nGameClockHours = ms_nGameClockHours;
+ ms_Stored_nGameClockMinutes = ms_nGameClockMinutes;
+ ms_Stored_nGameClockSeconds = ms_nGameClockSeconds;
+ ms_bClockHasBeenStored = true;
+}
+
+void
+CClock::RestoreClock(void)
+{
+ ms_nGameClockHours = ms_Stored_nGameClockHours;
+ ms_nGameClockMinutes = ms_Stored_nGameClockMinutes;
+ ms_nGameClockSeconds = ms_Stored_nGameClockSeconds;
+}
+
+
+STARTPATCHES
+ InjectHook(0x473370, CClock::Initialise, PATCH_JUMP);
+ InjectHook(0x473460, CClock::Update, PATCH_JUMP);
+ InjectHook(0x4733C0, CClock::SetGameClock, PATCH_JUMP);
+ InjectHook(0x4733F0, CClock::GetGameClockMinutesUntil, PATCH_JUMP);
+ InjectHook(0x473420, CClock::GetIsTimeInRange, PATCH_JUMP);
+ InjectHook(0x473540, CClock::StoreClock, PATCH_JUMP);
+ InjectHook(0x473570, CClock::RestoreClock, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Clock.h b/src/core/Clock.h
new file mode 100644
index 00000000..e11b2293
--- /dev/null
+++ b/src/core/Clock.h
@@ -0,0 +1,31 @@
+#pragma once
+
+class CClock
+{
+ static uint8 &ms_nGameClockHours;
+ static uint8 &ms_nGameClockMinutes;
+ static uint16 &ms_nGameClockSeconds;
+ static uint8 &ms_Stored_nGameClockHours;
+ static uint8 &ms_Stored_nGameClockMinutes;
+ static uint16 &ms_Stored_nGameClockSeconds;
+ static uint32 &ms_nMillisecondsPerGameMinute;
+ static int32 &ms_nLastClockTick;
+ static bool &ms_bClockHasBeenStored;
+public:
+
+ static void Initialise(uint32 scale);
+ static void Update(void);
+ static void SetGameClock(uint8 h, uint8 m);
+ static int32 GetGameClockMinutesUntil(uint8 h, uint8 m);
+ static bool GetIsTimeInRange(uint8 h1, uint8 h2);
+ static void StoreClock(void);
+ static void RestoreClock(void);
+
+ static uint8 GetHours(void) { return ms_nGameClockHours; }
+ static uint8 GetMinutes(void) { return ms_nGameClockMinutes; }
+ static int16 GetSeconds(void) { return ms_nGameClockSeconds; }
+
+
+ static uint8 &GetHoursRef(void) { return ms_nGameClockHours; }
+ static uint8 &GetMinutesRef(void) { return ms_nGameClockMinutes; }
+};
diff --git a/src/core/Collision.cpp b/src/core/Collision.cpp
new file mode 100644
index 00000000..d15ccca5
--- /dev/null
+++ b/src/core/Collision.cpp
@@ -0,0 +1,1887 @@
+#include "common.h"
+#include "patcher.h"
+#include "main.h"
+#include "Lists.h"
+#include "Game.h"
+#include "Zones.h"
+#include "General.h"
+#include "ZoneCull.h"
+#include "World.h"
+#include "Entity.h"
+#include "Train.h"
+#include "Streaming.h"
+#include "Pad.h"
+#include "DMAudio.h"
+#include "Population.h"
+#include "FileLoader.h"
+#include "Replay.h"
+#include "CutsceneMgr.h"
+#include "RenderBuffer.h"
+#include "SurfaceTable.h"
+#include "Collision.h"
+
+enum Direction
+{
+ DIR_X_POS,
+ DIR_X_NEG,
+ DIR_Y_POS,
+ DIR_Y_NEG,
+ DIR_Z_POS,
+ DIR_Z_NEG,
+};
+
+eLevelName &CCollision::ms_collisionInMemory = *(eLevelName*)0x8F6250;
+CLinkList<CColModel*> &CCollision::ms_colModelCache = *(CLinkList<CColModel*>*)0x95CB58;
+
+void
+CCollision::Init(void)
+{
+ ms_colModelCache.Init(NUMCOLCACHELINKS);
+ ms_collisionInMemory = LEVEL_NONE;
+}
+
+void
+CCollision::Shutdown(void)
+{
+ ms_colModelCache.Shutdown();
+}
+
+void
+CCollision::Update(void)
+{
+ CVector playerCoors;
+ playerCoors = FindPlayerCoors();
+ eLevelName level = CTheZones::m_CurrLevel;
+ bool forceLevelChange = false;
+
+ if(CTimer::GetTimeInMilliseconds() < 2000 || CCutsceneMgr::IsCutsceneProcessing())
+ return;
+
+ // hardcode a level if there are no zones
+ if(level == LEVEL_NONE){
+ if(CGame::currLevel == LEVEL_INDUSTRIAL &&
+ playerCoors.x < 400.0f){
+ level = LEVEL_COMMERCIAL;
+ forceLevelChange = true;
+ }else if(CGame::currLevel == LEVEL_SUBURBAN &&
+ playerCoors.x > -450.0f && playerCoors.y < -1400.0f){
+ level = LEVEL_COMMERCIAL;
+ forceLevelChange = true;
+ }else{
+ if(playerCoors.x > 800.0f){
+ level = LEVEL_INDUSTRIAL;
+ forceLevelChange = true;
+ }else if(playerCoors.x < -800.0f){
+ level = LEVEL_SUBURBAN;
+ forceLevelChange = true;
+ }
+ }
+ }
+ if(level != LEVEL_NONE && level != CGame::currLevel)
+ CGame::currLevel = level;
+ if(ms_collisionInMemory != CGame::currLevel)
+ LoadCollisionWhenINeedIt(forceLevelChange);
+ CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel);
+}
+
+eLevelName
+GetCollisionInSectorList(CPtrList &list)
+{
+ CPtrNode *node;
+ CEntity *e;
+ int level;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ level = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel()->level;
+ if(level != LEVEL_NONE)
+ return (eLevelName)level;
+ }
+ return LEVEL_NONE;
+}
+
+// Get a level this sector is in based on collision models
+eLevelName
+GetCollisionInSector(CSector &sect)
+{
+ int level;
+
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS]);
+ if(level == LEVEL_NONE)
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS_OVERLAP]);
+ if(level == LEVEL_NONE)
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS]);
+ if(level == LEVEL_NONE)
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS_OVERLAP]);
+ if(level == LEVEL_NONE)
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES]);
+ if(level == LEVEL_NONE)
+ level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES_OVERLAP]);
+ return (eLevelName)level;
+}
+
+void
+CCollision::LoadCollisionWhenINeedIt(bool forceChange)
+{
+ eLevelName level, l;
+ bool multipleLevels;
+ CVector playerCoors;
+ CVehicle *veh;
+ CEntryInfoNode *ei;
+ int sx, sy;
+ int xmin, xmax, ymin, ymax;
+ int x, y;
+
+ level = LEVEL_NONE;
+
+ playerCoors = FindPlayerCoors();
+ sx = CWorld::GetSectorIndexX(playerCoors.x);
+ sy = CWorld::GetSectorIndexY(playerCoors.y);
+ multipleLevels = false;
+
+ veh = FindPlayerVehicle();
+ if(veh && veh->IsTrain()){
+ if(((CTrain*)veh)->m_doorState != TRAIN_DOOR_STATE2)
+ return ;
+ }else if(playerCoors.z < 4.0f && !CCullZones::DoINeedToLoadCollision())
+ return;
+
+ // Figure out whose level's collisions we're most likely to be interested in
+ if(!forceChange){
+ if(veh && veh->IsBoat()){
+ // on water we expect to be between levels
+ multipleLevels = true;
+ }else{
+ xmin = max(sx - 1, 0);
+ xmax = min(sx + 1, NUMSECTORS_X-1);
+ ymin = max(sy - 1, 0);
+ ymax = min(sy + 1, NUMSECTORS_Y-1);
+
+ for(x = xmin; x <= xmax; x++)
+ for(y = ymin; y <= ymax; y++){
+ l = GetCollisionInSector(*CWorld::GetSector(x, y));
+ if(l != LEVEL_NONE){
+ if(level == LEVEL_NONE)
+ level = l;
+ if(level != l)
+ multipleLevels = true;
+ }
+ }
+ }
+
+ if(multipleLevels && veh && veh->IsBoat())
+ for(ei = veh->m_entryInfoList.first; ei; ei = ei->next){
+ level = GetCollisionInSector(*ei->sector);
+ if(level != LEVEL_NONE)
+ break;
+ }
+ }
+
+ if(level == CGame::currLevel || forceChange){
+ CTimer::Stop();
+ DMAudio.SetEffectsFadeVol(0);
+ CPad::StopPadsShaking();
+ LoadCollisionScreen(CGame::currLevel);
+ DMAudio.Service();
+ CPopulation::DealWithZoneChange(ms_collisionInMemory, CGame::currLevel, false);
+ CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL);
+ CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL);
+ CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN);
+ CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL);
+ CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL);
+ CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN);
+ CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel);
+ CStreaming::RemoveUnusedModelsInLoadedList();
+ CGame::TidyUpMemory(true, true);
+ CFileLoader::LoadCollisionFromDatFile(CGame::currLevel);
+ ms_collisionInMemory = CGame::currLevel;
+ CReplay::EmptyReplayBuffer();
+ if(CGame::currLevel != LEVEL_NONE)
+ LoadSplash(GetLevelSplashScreen(CGame::currLevel));
+ CStreaming::RemoveUnusedBigBuildings(CGame::currLevel);
+ CStreaming::RemoveUnusedBuildings(CGame::currLevel);
+ CStreaming::RequestBigBuildings(CGame::currLevel);
+ CStreaming::LoadAllRequestedModels(true);
+ CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel);
+ CGame::TidyUpMemory(true, true);
+ CTimer::Update();
+ DMAudio.SetEffectsFadeVol(127);
+ }
+}
+
+void
+CCollision::SortOutCollisionAfterLoad(void)
+{
+ if(ms_collisionInMemory == CGame::currLevel)
+ return;
+
+ CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel);
+ if(CGame::currLevel != LEVEL_NONE){
+ CFileLoader::LoadCollisionFromDatFile(CGame::currLevel);
+ if(!CGame::playingIntro)
+ LoadSplash(GetLevelSplashScreen(CGame::currLevel));
+ }
+ ms_collisionInMemory = CGame::currLevel;
+ CGame::TidyUpMemory(true, false);
+}
+
+void
+CCollision::LoadCollisionScreen(eLevelName level)
+{
+ static char *levelNames[4] = {
+ "",
+ "IND_ZON",
+ "COM_ZON",
+ "SUB_ZON"
+ };
+
+ // Why twice?
+ LoadingIslandScreen(levelNames[level]);
+ LoadingIslandScreen(levelNames[level]);
+}
+
+//
+// Test
+//
+
+
+bool
+CCollision::TestSphereSphere(const CColSphere &s1, const CColSphere &s2)
+{
+ float d = s1.radius + s2.radius;
+ return (s1.center - s2.center).MagnitudeSqr() < d*d;
+}
+
+bool
+CCollision::TestSphereBox(const CColSphere &sph, const CColBox &box)
+{
+ if(sph.center.x + sph.radius < box.min.x) return false;
+ if(sph.center.x - sph.radius > box.max.x) return false;
+ if(sph.center.y + sph.radius < box.min.y) return false;
+ if(sph.center.y - sph.radius > box.max.y) return false;
+ if(sph.center.z + sph.radius < box.min.z) return false;
+ if(sph.center.z - sph.radius > box.max.z) return false;
+ return true;
+}
+
+bool
+CCollision::TestLineBox(const CColLine &line, const CColBox &box)
+{
+ float t, x, y, z;
+ // If either line point is in the box, we have a collision
+ if(line.p0.x > box.min.x && line.p0.x < box.max.x &&
+ line.p0.y > box.min.y && line.p0.y < box.max.y &&
+ line.p0.z > box.min.z && line.p0.z < box.max.z)
+ return true;
+ if(line.p1.x > box.min.x && line.p1.x < box.max.x &&
+ line.p1.y > box.min.y && line.p1.y < box.max.y &&
+ line.p1.z > box.min.z && line.p1.z < box.max.z)
+ return true;
+
+ // check if points are on opposite sides of min x plane
+ if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){
+ // parameter along line where we intersect
+ t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x);
+ // y of intersection
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y){
+ // z of intersection
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ return true;
+ }
+ }
+
+ // same test with max x plane
+ if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){
+ t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x);
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ return true;
+ }
+ }
+
+ // min y plne
+ if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){
+ t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ return true;
+ }
+ }
+
+ // max y plane
+ if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){
+ t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ return true;
+ }
+ }
+
+ // min z plne
+ if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){
+ t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y)
+ return true;
+ }
+ }
+
+ // max z plane
+ if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){
+ t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y)
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CCollision::TestVerticalLineBox(const CColLine &line, const CColBox &box)
+{
+ if(line.p0.x <= box.min.x) return false;
+ if(line.p0.y <= box.min.y) return false;
+ if(line.p0.x >= box.max.x) return false;
+ if(line.p0.y >= box.max.y) return false;
+ if(line.p0.z < line.p1.z){
+ if(line.p0.z > box.max.z) return false;
+ if(line.p1.z < box.min.z) return false;
+ }else{
+ if(line.p1.z > box.max.z) return false;
+ if(line.p0.z < box.min.z) return false;
+ }
+ return true;
+}
+
+bool
+CCollision::TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane)
+{
+ float t;
+ CVector normal;
+ plane.GetNormal(normal);
+
+ // if points are on the same side, no collision
+ if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f)
+ return false;
+
+ // intersection parameter on line
+ t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal);
+ // find point of intersection
+ CVector p = line.p0 + (line.p1-line.p0)*t;
+
+ const CVector &va = verts[tri.a];
+ const CVector &vb = verts[tri.b];
+ const CVector &vc = verts[tri.c];
+ CVector2D vec1, vec2, vec3, vect;
+
+ // We do the test in 2D. With the plane direction we
+ // can figure out how to project the vectors.
+ // normal = (c-a) x (b-a)
+ switch(plane.dir){
+ case DIR_X_POS:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vc.y; vec2.y = vc.z;
+ vec3.x = vb.y; vec3.y = vb.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_X_NEG:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vb.y; vec2.y = vb.z;
+ vec3.x = vc.y; vec3.y = vc.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_Y_POS:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vc.z; vec2.y = vc.x;
+ vec3.x = vb.z; vec3.y = vb.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Y_NEG:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vb.z; vec2.y = vb.x;
+ vec3.x = vc.z; vec3.y = vc.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Z_POS:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vc.x; vec2.y = vc.y;
+ vec3.x = vb.x; vec3.y = vb.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ case DIR_Z_NEG:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vb.x; vec2.y = vb.y;
+ vec3.x = vc.x; vec3.y = vc.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ default:
+ assert(0);
+ }
+ // This is our triangle:
+ // 3-------2
+ // \ P /
+ // \ /
+ // \ /
+ // 1
+ // We can use the "2d cross product" to check on which side
+ // a vector is of another. Test is true if point is inside of all edges.
+ if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false;
+ if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false;
+ if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false;
+ return true;
+}
+
+// Test if line segment intersects with sphere.
+// If the first point is inside the sphere this test does not register a collision!
+// The code is reversed from the original code and rather ugly, see Process for a clear version.
+// TODO: actually rewrite this mess
+bool
+CCollision::TestLineSphere(const CColLine &line, const CColSphere &sph)
+{
+ CVector v01 = line.p1 - line.p0; // vector from p0 to p1
+ CVector v0c = sph.center - line.p0; // vector from p0 to center
+ float linesq = v01.MagnitudeSqr();
+ // I leave in the strange -2 factors even though they serve no real purpose
+ float projline = -2.0f * DotProduct(v01, v0c); // project v0c onto line
+ // Square of tangent from p0 multiplied by line length so we can compare with projline.
+ // The length of the tangent would be this: sqrt((c-p0)^2 - r^2).
+ // Negative if p0 is inside the sphere! This breaks the test!
+ float tansq = 4.0f * linesq *
+ (sph.center.MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius);
+ float diffsq = projline*projline - tansq;
+ // if diffsq < 0 that means the line is a passant, so no intersection
+ if(diffsq < 0.0f)
+ return false;
+ // projline (negative in GTA for some reason) is the point on the line
+ // in the middle of the two intersection points (startin from p0).
+ // sqrt(diffsq) somehow works out to be the distance from that
+ // midpoint to the intersection points.
+ // So subtract that and get rid of the awkward scaling:
+ float f = (-projline - sqrt(diffsq)) / (2.0f*linesq);
+ // f should now be in range [0, 1] for [p0, p1]
+ return f >= 0.0f && f <= 1.0f;
+}
+
+bool
+CCollision::TestSphereTriangle(const CColSphere &sphere,
+ const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane)
+{
+ // If sphere and plane don't intersect, no collision
+ if(fabs(plane.CalcPoint(sphere.center)) > sphere.radius)
+ return false;
+
+ const CVector &va = verts[tri.a];
+ const CVector &vb = verts[tri.b];
+ const CVector &vc = verts[tri.c];
+
+ // calculate two orthogonal basis vectors for the triangle
+ CVector vec2 = vb - va;
+ float len = vec2.Magnitude();
+ vec2 = vec2 * (1.0f/len);
+ CVector vec1 = CrossProduct(vec2, plane.normal);
+
+ // We know A has local coordinate [0,0] and B has [0,len].
+ // Now calculate coordinates on triangle for these two vectors:
+ CVector vac = vc - va;
+ CVector vas = sphere.center - va;
+ CVector2D b(0.0f, len);
+ CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac));
+ CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas));
+
+ // The three triangle lines partition the space into 6 sectors,
+ // find out in which the center lies.
+ int insideAB = CrossProduct2D(s, b) >= 0.0f;
+ int insideAC = CrossProduct2D(c, s) >= 0.0f;
+ int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f;
+
+ int testcase = insideAB + insideAC + insideBC;
+ float dist = 0.0f;
+ if(testcase == 1){
+ // closest to a vertex
+ if(insideAB) dist = (sphere.center - vc).Magnitude();
+ else if(insideAC) dist = (sphere.center - vb).Magnitude();
+ else if(insideBC) dist = (sphere.center - va).Magnitude();
+ else assert(0);
+ }else if(testcase == 2){
+ // closest to an edge
+ if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center);
+ else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center);
+ else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center);
+ else assert(0);
+ }else if(testcase == 3){
+ // center is in triangle
+ return true;
+ }else
+ assert(0); // front fell off
+
+ return dist < sphere.radius;
+}
+
+bool
+CCollision::TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough)
+{
+ static CMatrix matTransform;
+ int i;
+
+ // transform line to model space
+ Invert(matrix, matTransform);
+ CColLine newline(matTransform * line.p0, matTransform * line.p1);
+
+ // If we don't intersect with the bounding box, no chance on the rest
+ if(!TestLineBox(newline, model.boundingBox))
+ return false;
+
+ for(i = 0; i < model.numSpheres; i++)
+ if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD)
+ if(TestLineSphere(newline, model.spheres[i]))
+ return true;
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD)
+ if(TestLineBox(newline, model.boxes[i]))
+ return true;
+
+ CalculateTrianglePlanes(&model);
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD)
+ if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i]))
+ return true;
+
+ return false;
+}
+
+
+//
+// Process
+//
+
+// For Spheres mindist is the squared distance to its center
+// For Lines mindist is between [0,1]
+
+bool
+CCollision::ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq)
+{
+ CVector dist = s1.center - s2.center;
+ float d = dist.Magnitude() - s2.radius; // distance from s1's center to s2
+ float dc = d < 0.0f ? 0.0f : d; // clamp to zero, i.e. if s1's center is inside s2
+ // no collision if sphere is not close enough
+ if(mindistsq <= dc*dc || s1.radius <= dc)
+ return false;
+ dist.Normalise();
+ point.point = s1.center - dist*dc;
+ point.normal = dist;
+ point.surfaceA = s1.surface;
+ point.pieceA = s1.piece;
+ point.surfaceB = s2.surface;
+ point.pieceB = s2.piece;
+ point.depth = s1.radius - d; // sphere overlap
+ mindistsq = dc*dc; // collision radius
+ return true;
+}
+
+bool
+CCollision::ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq)
+{
+ CVector p;
+ CVector dist;
+
+ // GTA's code is too complicated, uses a huge 3x3x3 if statement
+ // we can simplify the structure a lot
+
+ // first make sure we have a collision at all
+ if(sph.center.x + sph.radius < box.min.x) return false;
+ if(sph.center.x - sph.radius > box.max.x) return false;
+ if(sph.center.y + sph.radius < box.min.y) return false;
+ if(sph.center.y - sph.radius > box.max.y) return false;
+ if(sph.center.z + sph.radius < box.min.z) return false;
+ if(sph.center.z - sph.radius > box.max.z) return false;
+
+ // Now find out where the sphere center lies in relation to all the sides
+ int xpos = sph.center.x < box.min.x ? 1 :
+ sph.center.x > box.max.x ? 2 :
+ 0;
+ int ypos = sph.center.y < box.min.y ? 1 :
+ sph.center.y > box.max.y ? 2 :
+ 0;
+ int zpos = sph.center.z < box.min.z ? 1 :
+ sph.center.z > box.max.z ? 2 :
+ 0;
+
+ if(xpos == 0 && ypos == 0 && zpos == 0){
+ // sphere is inside the box
+ p = (box.min + box.max)*0.5f;
+
+ dist = sph.center - p;
+ float lensq = dist.MagnitudeSqr();
+ if(lensq < mindistsq){
+ point.normal = dist * (1.0f/sqrt(lensq));
+ point.point = sph.center - point.normal;
+ point.surfaceA = sph.surface;
+ point.pieceA = sph.piece;
+ point.surfaceB = box.surface;
+ point.pieceB = box.piece;
+
+ // find absolute distance to the closer side in each dimension
+ float dx = dist.x > 0.0f ?
+ box.max.x - sph.center.x :
+ sph.center.x - box.min.x;
+ float dy = dist.y > 0.0f ?
+ box.max.y - sph.center.y :
+ sph.center.y - box.min.y;
+ float dz = dist.z > 0.0f ?
+ box.max.z - sph.center.z :
+ sph.center.z - box.min.z;
+ // collision depth is maximum of that:
+ if(dx > dy && dx > dz)
+ point.depth = dx;
+ else if(dy > dz)
+ point.depth = dy;
+ else
+ point.depth = dz;
+ return true;
+ }
+ }else{
+ // sphere is outside.
+ // closest point on box:
+ p.x = xpos == 1 ? box.min.x :
+ xpos == 2 ? box.max.x :
+ sph.center.x;
+ p.y = ypos == 1 ? box.min.y :
+ ypos == 2 ? box.max.y :
+ sph.center.y;
+ p.z = zpos == 1 ? box.min.z :
+ zpos == 2 ? box.max.z :
+ sph.center.z;
+
+ dist = sph.center - p;
+ float lensq = dist.MagnitudeSqr();
+ if(lensq < mindistsq){
+ float len = sqrt(lensq);
+ point.point = p;
+ point.normal = dist * (1.0f/len);
+ point.surfaceA = sph.surface;
+ point.pieceA = sph.piece;
+ point.surfaceB = box.surface;
+ point.pieceB = box.piece;
+ point.depth = sph.radius - len;
+ mindistsq = lensq;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CCollision::ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist)
+{
+ float mint, t, x, y, z;
+ CVector normal;
+ CVector p;
+
+ mint = 1.0f;
+ // check if points are on opposite sides of min x plane
+ if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){
+ // parameter along line where we intersect
+ t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x);
+ // y of intersection
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y){
+ // z of intersection
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ if(t < mint){
+ mint = t;
+ p = CVector(box.min.x, y, z);
+ normal = CVector(-1.0f, 0.0f, 0.0f);
+ }
+ }
+ }
+
+ // max x plane
+ if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){
+ t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x);
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ if(t < mint){
+ mint = t;
+ p = CVector(box.max.x, y, z);
+ normal = CVector(1.0f, 0.0f, 0.0f);
+ }
+ }
+ }
+
+ // min y plne
+ if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){
+ t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ if(t < mint){
+ mint = t;
+ p = CVector(x, box.min.y, z);
+ normal = CVector(0.0f, -1.0f, 0.0f);
+ }
+ }
+ }
+
+ // max y plane
+ if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){
+ t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ z = line.p0.z + (line.p1.z - line.p0.z)*t;
+ if(z > box.min.z && z < box.max.z)
+ if(t < mint){
+ mint = t;
+ p = CVector(x, box.max.y, z);
+ normal = CVector(0.0f, 1.0f, 0.0f);
+ }
+ }
+ }
+
+ // min z plne
+ if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){
+ t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y)
+ if(t < mint){
+ mint = t;
+ p = CVector(x, y, box.min.z);
+ normal = CVector(0.0f, 0.0f, -1.0f);
+ }
+ }
+ }
+
+ // max z plane
+ if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){
+ t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z);
+ x = line.p0.x + (line.p1.x - line.p0.x)*t;
+ if(x > box.min.x && x < box.max.x){
+ y = line.p0.y + (line.p1.y - line.p0.y)*t;
+ if(y > box.min.y && y < box.max.y)
+ if(t < mint){
+ mint = t;
+ p = CVector(x, y, box.max.z);
+ normal = CVector(0.0f, 0.0f, 1.0f);
+ }
+ }
+ }
+
+ if(mint >= mindist)
+ return false;
+
+ point.point = p;
+ point.normal = normal;
+ point.surfaceA = 0;
+ point.pieceA = 0;
+ point.surfaceB = box.surface;
+ point.pieceB = box.piece;
+ mindist = mint;
+
+ return true;
+}
+
+// If line.p0 lies inside sphere, no collision is registered.
+bool
+CCollision::ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist)
+{
+ CVector v01 = line.p1 - line.p0;
+ CVector v0c = sphere.center - line.p0;
+ float linesq = v01.MagnitudeSqr();
+ // project v0c onto v01, scaled by |v01| this is the midpoint of the two intersections
+ float projline = DotProduct(v01, v0c);
+ // tangent of p0 to sphere, scaled by linesq just like projline^2
+ float tansq = (v0c.MagnitudeSqr() - sphere.radius*sphere.radius) * linesq;
+ // this works out to be the square of the distance between the midpoint and the intersections
+ float diffsq = projline*projline - tansq;
+ // no intersection
+ if(diffsq < 0.0f)
+ return false;
+ // point of first intersection, in range [0,1] between p0 and p1
+ float t = (projline - sqrt(diffsq)) / linesq;
+ // if not on line or beyond mindist, no intersection
+ if(t < 0.0f || t > 1.0f || t >= mindist)
+ return false;
+ point.point = line.p0 + v01*t;
+ point.normal = point.point - sphere.center;
+ point.normal.Normalise();
+ point.surfaceA = 0;
+ point.pieceA = 0;
+ point.surfaceB = sphere.surface;
+ point.pieceB = sphere.piece;
+ mindist = t;
+ return true;
+}
+
+bool
+CCollision::ProcessVerticalLineTriangle(const CColLine &line,
+ const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane,
+ CColPoint &point, float &mindist, CStoredCollPoly *poly)
+{
+ float t;
+ CVector normal;
+
+ const CVector &p0 = line.p0;
+ const CVector &va = verts[tri.a];
+ const CVector &vb = verts[tri.b];
+ const CVector &vc = verts[tri.c];
+
+ // early out bound rect test
+ if(p0.x < va.x && p0.x < vb.x && p0.x < vc.x) return false;
+ if(p0.x > va.x && p0.x > vb.x && p0.x > vc.x) return false;
+ if(p0.y < va.y && p0.y < vb.y && p0.y < vc.y) return false;
+ if(p0.y > va.y && p0.y > vb.y && p0.y > vc.y) return false;
+
+ plane.GetNormal(normal);
+ // if points are on the same side, no collision
+ if(plane.CalcPoint(p0) * plane.CalcPoint(line.p1) > 0.0f)
+ return false;
+
+ // intersection parameter on line
+ float h = (line.p1 - p0).z;
+ t = -plane.CalcPoint(p0) / (h * normal.z);
+ // early out if we're beyond the mindist
+ if(t >= mindist)
+ return false;
+ CVector p(p0.x, p0.y, p0.z + h*t);
+
+ CVector2D vec1, vec2, vec3, vect;
+ switch(plane.dir){
+ case DIR_X_POS:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vc.y; vec2.y = vc.z;
+ vec3.x = vb.y; vec3.y = vb.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_X_NEG:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vb.y; vec2.y = vb.z;
+ vec3.x = vc.y; vec3.y = vc.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_Y_POS:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vc.z; vec2.y = vc.x;
+ vec3.x = vb.z; vec3.y = vb.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Y_NEG:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vb.z; vec2.y = vb.x;
+ vec3.x = vc.z; vec3.y = vc.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Z_POS:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vc.x; vec2.y = vc.y;
+ vec3.x = vb.x; vec3.y = vb.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ case DIR_Z_NEG:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vb.x; vec2.y = vb.y;
+ vec3.x = vc.x; vec3.y = vc.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ default:
+ assert(0);
+ }
+ if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false;
+ if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false;
+ if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false;
+ point.point = p;
+ point.normal = normal;
+ point.surfaceA = 0;
+ point.pieceA = 0;
+ point.surfaceB = tri.surface;
+ point.pieceB = 0;
+ if(poly){
+ poly->verts[0] = va;
+ poly->verts[1] = vb;
+ poly->verts[2] = vc;
+ poly->valid = true;
+ }
+ mindist = t;
+ return true;
+}
+
+bool
+CCollision::ProcessLineTriangle(const CColLine &line ,
+ const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane,
+ CColPoint &point, float &mindist)
+{
+ float t;
+ CVector normal;
+ plane.GetNormal(normal);
+
+ // if points are on the same side, no collision
+ if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f)
+ return false;
+
+ // intersection parameter on line
+ t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal);
+ // early out if we're beyond the mindist
+ if(t >= mindist)
+ return false;
+ // find point of intersection
+ CVector p = line.p0 + (line.p1-line.p0)*t;
+
+ const CVector &va = verts[tri.a];
+ const CVector &vb = verts[tri.b];
+ const CVector &vc = verts[tri.c];
+ CVector2D vec1, vec2, vec3, vect;
+
+ switch(plane.dir){
+ case DIR_X_POS:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vc.y; vec2.y = vc.z;
+ vec3.x = vb.y; vec3.y = vb.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_X_NEG:
+ vec1.x = va.y; vec1.y = va.z;
+ vec2.x = vb.y; vec2.y = vb.z;
+ vec3.x = vc.y; vec3.y = vc.z;
+ vect.x = p.y; vect.y = p.z;
+ break;
+ case DIR_Y_POS:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vc.z; vec2.y = vc.x;
+ vec3.x = vb.z; vec3.y = vb.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Y_NEG:
+ vec1.x = va.z; vec1.y = va.x;
+ vec2.x = vb.z; vec2.y = vb.x;
+ vec3.x = vc.z; vec3.y = vc.x;
+ vect.x = p.z; vect.y = p.x;
+ break;
+ case DIR_Z_POS:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vc.x; vec2.y = vc.y;
+ vec3.x = vb.x; vec3.y = vb.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ case DIR_Z_NEG:
+ vec1.x = va.x; vec1.y = va.y;
+ vec2.x = vb.x; vec2.y = vb.y;
+ vec3.x = vc.x; vec3.y = vc.y;
+ vect.x = p.x; vect.y = p.y;
+ break;
+ default:
+ assert(0);
+ }
+ if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false;
+ if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false;
+ if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false;
+ point.point = p;
+ point.normal = normal;
+ point.surfaceA = 0;
+ point.pieceA = 0;
+ point.surfaceB = tri.surface;
+ point.pieceB = 0;
+ mindist = t;
+ return true;
+}
+
+bool
+CCollision::ProcessSphereTriangle(const CColSphere &sphere,
+ const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane,
+ CColPoint &point, float &mindistsq)
+{
+ // If sphere and plane don't intersect, no collision
+ float planedist = plane.CalcPoint(sphere.center);
+ float distsq = planedist*planedist;
+ if(fabs(planedist) > sphere.radius || distsq > mindistsq)
+ return false;
+
+ const CVector &va = verts[tri.a];
+ const CVector &vb = verts[tri.b];
+ const CVector &vc = verts[tri.c];
+
+ // calculate two orthogonal basis vectors for the triangle
+ CVector normal;
+ plane.GetNormal(normal);
+ CVector vec2 = vb - va;
+ float len = vec2.Magnitude();
+ vec2 = vec2 * (1.0f/len);
+ CVector vec1 = CrossProduct(vec2, normal);
+
+ // We know A has local coordinate [0,0] and B has [0,len].
+ // Now calculate coordinates on triangle for these two vectors:
+ CVector vac = vc - va;
+ CVector vas = sphere.center - va;
+ CVector2D b(0.0f, len);
+ CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac));
+ CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas));
+
+ // The three triangle lines partition the space into 6 sectors,
+ // find out in which the center lies.
+ int insideAB = CrossProduct2D(s, b) >= 0.0f;
+ int insideAC = CrossProduct2D(c, s) >= 0.0f;
+ int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f;
+
+ int testcase = insideAB + insideAC + insideBC;
+ float dist = 0.0f;
+ CVector p;
+ if(testcase == 1){
+ // closest to a vertex
+ if(insideAB) p = vc;
+ else if(insideAC) p = vb;
+ else if(insideBC) p = va;
+ else assert(0);
+ dist = (sphere.center - p).Magnitude();
+ }else if(testcase == 2){
+ // closest to an edge
+ if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center, p);
+ else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center, p);
+ else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center, p);
+ else assert(0);
+ }else if(testcase == 3){
+ // center is in triangle
+ dist = fabs(planedist);
+ p = sphere.center - normal*planedist;
+ }else
+ assert(0); // front fell off
+
+ if(dist >= sphere.radius || dist*dist >= mindistsq)
+ return false;
+
+ point.point = p;
+ point.normal = sphere.center - p;
+ point.normal.Normalise();
+ point.surfaceA = sphere.surface;
+ point.pieceA = sphere.piece;
+ point.surfaceB = tri.surface;
+ point.pieceB = 0;
+ point.depth = sphere.radius - dist;
+ mindistsq = dist*dist;
+ return true;
+}
+
+bool
+CCollision::ProcessLineOfSight(const CColLine &line,
+ const CMatrix &matrix, CColModel &model,
+ CColPoint &point, float &mindist, bool ignoreSeeThrough)
+{
+ static CMatrix matTransform;
+ int i;
+
+ // transform line to model space
+ Invert(matrix, matTransform);
+ CColLine newline(matTransform * line.p0, matTransform * line.p1);
+
+ // If we don't intersect with the bounding box, no chance on the rest
+ if(!TestLineBox(newline, model.boundingBox))
+ return false;
+
+ float coldist = mindist;
+ for(i = 0; i < model.numSpheres; i++)
+ if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD)
+ ProcessLineSphere(newline, model.spheres[i], point, coldist);
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD)
+ ProcessLineBox(newline, model.boxes[i], point, coldist);
+
+ CalculateTrianglePlanes(&model);
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD)
+ ProcessLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist);
+
+ if(coldist < mindist){
+ point.point = matrix * point.point;
+ point.normal = Multiply3x3(matrix, point.normal);
+ mindist = coldist;
+ return true;
+ }
+ return false;
+}
+
+bool
+CCollision::ProcessVerticalLine(const CColLine &line,
+ const CMatrix &matrix, CColModel &model,
+ CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly)
+{
+ static CStoredCollPoly TempStoredPoly;
+ int i;
+
+ // transform line to model space
+ // Why does the game seem to do this differently than above?
+ CColLine newline(MultiplyInverse(matrix, line.p0), MultiplyInverse(matrix, line.p1));
+ newline.p1.x = newline.p0.x;
+ newline.p1.y = newline.p0.y;
+
+ if(!TestVerticalLineBox(newline, model.boundingBox))
+ return false;
+
+ float coldist = mindist;
+ for(i = 0; i < model.numSpheres; i++)
+ if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD)
+ ProcessLineSphere(newline, model.spheres[i], point, coldist);
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD)
+ ProcessLineBox(newline, model.boxes[i], point, coldist);
+
+ CalculateTrianglePlanes(&model);
+ TempStoredPoly.valid = false;
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD)
+ ProcessVerticalLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist, &TempStoredPoly);
+
+ if(coldist < mindist){
+ point.point = matrix * point.point;
+ point.normal = Multiply3x3(matrix, point.normal);
+ if(poly && TempStoredPoly.valid){
+ *poly = TempStoredPoly;
+ poly->verts[0] = matrix * poly->verts[0];
+ poly->verts[1] = matrix * poly->verts[1];
+ poly->verts[2] = matrix * poly->verts[2];
+ }
+ mindist = coldist;
+ return true;
+ }
+ return false;
+}
+
+enum {
+ MAXNUMSPHERES = 128,
+ MAXNUMBOXES = 32,
+ MAXNUMLINES = 16,
+ MAXNUMTRIS = 600
+};
+
+// This checks model A's spheres and lines against model B's spheres, boxes and triangles.
+// Returns the number of A's spheres that collide.
+// Returned ColPoints are in world space.
+// NB: lines do not seem to be supported very well, use with caution
+int32
+CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA,
+ const CMatrix &matrixB, CColModel &modelB,
+ CColPoint *spherepoints, CColPoint *linepoints, float *linedists)
+{
+ static int aSphereIndicesA[MAXNUMSPHERES];
+ static int aLineIndicesA[MAXNUMLINES];
+ static int aSphereIndicesB[MAXNUMSPHERES];
+ static int aBoxIndicesB[MAXNUMBOXES];
+ static int aTriangleIndicesB[MAXNUMTRIS];
+ static bool aCollided[MAXNUMLINES];
+ static CColSphere aSpheresA[MAXNUMSPHERES];
+ static CColLine aLinesA[MAXNUMLINES];
+ static CMatrix matAB, matBA;
+ CColSphere s;
+ int i, j;
+
+ assert(modelA.numSpheres <= MAXNUMSPHERES);
+ assert(modelA.numLines <= MAXNUMLINES);
+
+ // From model A space to model B space
+ matAB = Invert(matrixB, matAB) * matrixA;
+
+ CColSphere bsphereAB; // bounding sphere of A in B space
+ bsphereAB.Set(modelA.boundingSphere.radius, matAB * modelA.boundingSphere.center);
+ if(!TestSphereBox(bsphereAB, modelB.boundingBox))
+ return 0;
+ // B to A space
+ matBA = Invert(matrixA, matBA) * matrixB;
+
+ // transform modelA's spheres and lines to B space
+ for(i = 0; i < modelA.numSpheres; i++){
+ CColSphere &s = modelA.spheres[i];
+ aSpheresA[i].Set(s.radius, matAB * s.center, s.surface, s.piece);
+ }
+ for(i = 0; i < modelA.numLines; i++)
+ aLinesA[i].Set(matAB * modelA.lines[i].p0, matAB * modelA.lines[i].p1);
+
+ // Test them against model B's bounding volumes
+ int numSpheresA = 0;
+ int numLinesA = 0;
+ for(i = 0; i < modelA.numSpheres; i++)
+ if(TestSphereBox(aSpheresA[i], modelB.boundingBox))
+ aSphereIndicesA[numSpheresA++] = i;
+ // no actual check???
+ for(i = 0; i < modelA.numLines; i++)
+ aLineIndicesA[numLinesA++] = i;
+ // No collision
+ if(numSpheresA == 0 && numLinesA == 0)
+ return 0;
+
+ // Check model B against A's bounding volumes
+ int numSpheresB = 0;
+ int numBoxesB = 0;
+ int numTrianglesB = 0;
+ for(i = 0; i < modelB.numSpheres; i++){
+ s.Set(modelB.spheres[i].radius, matBA * modelB.spheres[i].center);
+ if(TestSphereBox(s, modelA.boundingBox))
+ aSphereIndicesB[numSpheresB++] = i;
+ }
+ for(i = 0; i < modelB.numBoxes; i++)
+ if(TestSphereBox(bsphereAB, modelB.boxes[i]))
+ aBoxIndicesB[numBoxesB++] = i;
+ CalculateTrianglePlanes(&modelB);
+ for(i = 0; i < modelB.numTriangles; i++)
+ if(TestSphereTriangle(bsphereAB, modelB.vertices, modelB.triangles[i], modelB.trianglePlanes[i]))
+ aTriangleIndicesB[numTrianglesB++] = i;
+ assert(numSpheresB <= MAXNUMSPHERES);
+ assert(numBoxesB <= MAXNUMBOXES);
+ assert(numTrianglesB <= MAXNUMTRIS);
+ // No collision
+ if(numSpheresB == 0 && numBoxesB == 0 && numTrianglesB == 0)
+ return 0;
+
+ // We now have the collision volumes in A and B that are worth processing.
+
+ // Process A's spheres against B's collision volumes
+ int numCollisions = 0;
+ for(i = 0; i < numSpheresA; i++){
+ float coldist = 1.0e24f;
+ bool hasCollided = false;
+
+ for(j = 0; j < numSpheresB; j++)
+ hasCollided |= ProcessSphereSphere(
+ aSpheresA[aSphereIndicesA[i]],
+ modelB.spheres[aSphereIndicesB[j]],
+ spherepoints[numCollisions], coldist);
+ for(j = 0; j < numBoxesB; j++)
+ hasCollided |= ProcessSphereBox(
+ aSpheresA[aSphereIndicesA[i]],
+ modelB.boxes[aBoxIndicesB[j]],
+ spherepoints[numCollisions], coldist);
+ for(j = 0; j < numTrianglesB; j++)
+ hasCollided |= ProcessSphereTriangle(
+ aSpheresA[aSphereIndicesA[i]],
+ modelB.vertices,
+ modelB.triangles[aTriangleIndicesB[j]],
+ modelB.trianglePlanes[aTriangleIndicesB[j]],
+ spherepoints[numCollisions], coldist);
+ if(hasCollided)
+ numCollisions++;
+ }
+ for(i = 0; i < numCollisions; i++){
+ spherepoints[i].point = matrixB * spherepoints[i].point;
+ spherepoints[i].normal = Multiply3x3(matrixB, spherepoints[i].normal);
+ }
+
+ // And the same thing for the lines in A
+ for(i = 0; i < numLinesA; i++){
+ aCollided[i] = false;
+
+ for(j = 0; j < numSpheresB; j++)
+ aCollided[i] |= ProcessLineSphere(
+ aLinesA[aLineIndicesA[i]],
+ modelB.spheres[aSphereIndicesB[j]],
+ linepoints[aLineIndicesA[i]],
+ linedists[aLineIndicesA[i]]);
+ for(j = 0; j < numBoxesB; j++)
+ aCollided[i] |= ProcessLineBox(
+ aLinesA[aLineIndicesA[i]],
+ modelB.boxes[aBoxIndicesB[j]],
+ linepoints[aLineIndicesA[i]],
+ linedists[aLineIndicesA[i]]);
+ for(j = 0; j < numTrianglesB; j++)
+ aCollided[i] |= ProcessLineTriangle(
+ aLinesA[aLineIndicesA[i]],
+ modelB.vertices,
+ modelB.triangles[aTriangleIndicesB[j]],
+ modelB.trianglePlanes[aTriangleIndicesB[j]],
+ linepoints[aLineIndicesA[i]],
+ linedists[aLineIndicesA[i]]);
+ }
+ for(i = 0; i < numLinesA; i++)
+ if(aCollided[i]){
+ j = aLineIndicesA[i];
+ linepoints[j].point = matrixB * linepoints[j].point;
+ linepoints[j].normal = Multiply3x3(matrixB, linepoints[j].normal);
+ }
+
+ return numCollisions; // sphere collisions
+}
+
+
+//
+// Misc
+//
+
+float
+CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point)
+{
+ float lensq = (*l1 - *l0).MagnitudeSqr();
+ float dot = DotProduct(*point - *l0, *l1 - *l0);
+ // Between 0 and len we're above the line.
+ // if not, calculate distance to endpoint
+ if(dot <= 0.0f)
+ return (*point - *l0).Magnitude();
+ if(dot >= lensq)
+ return (*point - *l1).Magnitude();
+ // distance to line
+ return sqrt((*point - *l0).MagnitudeSqr() - dot*dot/lensq);
+}
+
+// same as above but also return the point on the line
+float
+CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest)
+{
+ float lensq = (*l1 - *l0).MagnitudeSqr();
+ float dot = DotProduct(*point - *l0, *l1 - *l0);
+ // find out which point we're closest to
+ if(dot <= 0.0f)
+ closest = *l0;
+ else if(dot >= lensq)
+ closest = *l1;
+ else
+ closest = *l0 + (*l1 - *l0)*(dot/lensq);
+ // this is the distance
+ return (*point - closest).Magnitude();
+}
+
+void
+CCollision::CalculateTrianglePlanes(CColModel *model)
+{
+ if(model->numTriangles == 0)
+ return;
+
+ CLink<CColModel*> *lptr;
+ if(model->trianglePlanes){
+ // re-insert at front so it's not removed again soon
+ lptr = model->GetLinkPtr();
+ lptr->Remove();
+ ms_colModelCache.head.Insert(lptr);
+ }else{
+ assert(model);
+ lptr = ms_colModelCache.Insert(model);
+ if(lptr == nil){
+ // make room if we have to, remove last in list
+ lptr = ms_colModelCache.tail.prev;
+ assert(lptr);
+ assert(lptr->item);
+ lptr->item->RemoveTrianglePlanes();
+ ms_colModelCache.Remove(lptr);
+ // now this cannot fail
+ lptr = ms_colModelCache.Insert(model);
+ assert(lptr);
+ }
+ model->CalculateTrianglePlanes();
+ model->SetLinkPtr(lptr);
+ }
+}
+
+void
+CCollision::DrawColModel(const CMatrix &mat, const CColModel &colModel)
+{
+}
+
+void
+CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id)
+{
+ int i;
+ int s;
+ float f;
+ CVector verts[8];
+ CVector min, max;
+ int r, g, b;
+ RwImVertexIndex *iptr;
+ RwIm3DVertex *vptr;
+
+ RenderBuffer::ClearRenderBuffer();
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+extern int gDbgSurf;
+
+ for(i = 0; i < colModel.numTriangles; i++){
+ colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a);
+ colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b);
+ colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c);
+ verts[0] = mat * verts[0];
+ verts[1] = mat * verts[1];
+ verts[2] = mat * verts[2];
+
+ // TODO: surface
+ r = 255;
+ g = 128;
+ b = 0;
+
+ s = colModel.triangles[i].surface;
+ f = (s & 0xF)/32.0f + 0.5f;
+ switch(CSurfaceTable::GetAdhesionGroup(s)){
+ case ADHESIVE_RUBBER:
+ r = f * 255.0f;
+ g = 0;
+ b = 0;
+ break;
+ case ADHESIVE_HARD:
+ r = f*255.0f;
+ g = f*255.0f;
+ b = f*128.0f;
+ break;
+ case ADHESIVE_ROAD:
+ r = f*128.0f;
+ g = f*128.0f;
+ b = f*128.0f;
+ break;
+ case ADHESIVE_LOOSE:
+ r = 0;
+ g = f * 255.0f;
+ b = 0;
+ break;
+ case ADHESIVE_WET:
+ r = 0;
+ g = 0;
+ b = f * 255.0f;
+ break;
+ default:
+ // this doesn't make much sense
+ r *= f;
+ g *= f;
+ b *= f;
+ }
+
+ // TODO: make some surface types flicker?
+//if(s != gDbgSurf) continue;
+
+ if(s > SURFACE_32){
+ r = CGeneral::GetRandomNumber();
+ g = CGeneral::GetRandomNumber();
+ b = CGeneral::GetRandomNumber();
+ printf("Illegal surfacetype:%d on MI:%d\n", s, id);
+ }
+
+ RenderBuffer::StartStoring(6, 3, &iptr, &vptr);
+ RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255);
+ RwIm3DVertexSetU(&vptr[0], 0.0f);
+ RwIm3DVertexSetV(&vptr[0], 0.0f);
+ RwIm3DVertexSetU(&vptr[1], 0.0f);
+ RwIm3DVertexSetV(&vptr[1], 1.0f);
+ RwIm3DVertexSetU(&vptr[2], 1.0f);
+ RwIm3DVertexSetV(&vptr[2], 1.0f);
+ RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z);
+ RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z);
+ RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z);
+ iptr[0] = 0; iptr[1] = 1; iptr[2] = 2;
+ iptr[3] = 0; iptr[4] = 2; iptr[5] = 1;
+ RenderBuffer::StopStoring();
+ }
+
+ for(i = 0; i < colModel.numBoxes; i++){
+ min = colModel.boxes[i].min;
+ max = colModel.boxes[i].max;
+
+ verts[0] = mat * CVector(min.x, min.y, min.z);
+ verts[1] = mat * CVector(min.x, min.y, max.z);
+ verts[2] = mat * CVector(min.x, max.y, min.z);
+ verts[3] = mat * CVector(min.x, max.y, max.z);
+ verts[4] = mat * CVector(max.x, min.y, min.z);
+ verts[5] = mat * CVector(max.x, min.y, max.z);
+ verts[6] = mat * CVector(max.x, max.y, min.z);
+ verts[7] = mat * CVector(max.x, max.y, max.z);
+
+ s = colModel.boxes[i].surface;
+ f = (s & 0xF)/32.0f + 0.5f;
+ switch(CSurfaceTable::GetAdhesionGroup(s)){
+ case ADHESIVE_RUBBER:
+ r = f * 255.0f;
+ g = 0;
+ b = 0;
+ break;
+ case ADHESIVE_HARD:
+ r = f*255.0f;
+ g = f*255.0f;
+ b = f*128.0f;
+ break;
+ case ADHESIVE_ROAD:
+ r = f*128.0f;
+ g = f*128.0f;
+ b = f*128.0f;
+ break;
+ case ADHESIVE_LOOSE:
+ r = 0;
+ g = f * 255.0f;
+ b = 0;
+ break;
+ case ADHESIVE_WET:
+ r = 0;
+ g = 0;
+ b = f * 255.0f;
+ break;
+ default:
+ // this doesn't make much sense
+ r *= f;
+ g *= f;
+ b *= f;
+ }
+
+ // TODO: make some surface types flicker?
+//if(s != gDbgSurf) continue;
+
+ RenderBuffer::StartStoring(36, 8, &iptr, &vptr);
+ RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[3], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[4], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[5], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[6], r, g, b, 255);
+ RwIm3DVertexSetRGBA(&vptr[7], r, g, b, 255);
+ RwIm3DVertexSetU(&vptr[0], 0.0f);
+ RwIm3DVertexSetV(&vptr[0], 0.0f);
+ RwIm3DVertexSetU(&vptr[1], 0.0f);
+ RwIm3DVertexSetV(&vptr[1], 1.0f);
+ RwIm3DVertexSetU(&vptr[2], 1.0f);
+ RwIm3DVertexSetV(&vptr[2], 1.0f);
+ RwIm3DVertexSetU(&vptr[3], 0.0f);
+ RwIm3DVertexSetV(&vptr[3], 0.0f);
+ RwIm3DVertexSetU(&vptr[4], 0.0f);
+ RwIm3DVertexSetV(&vptr[4], 1.0f);
+ RwIm3DVertexSetU(&vptr[5], 1.0f);
+ RwIm3DVertexSetV(&vptr[5], 1.0f);
+ RwIm3DVertexSetU(&vptr[6], 0.0f);
+ RwIm3DVertexSetV(&vptr[6], 1.0f);
+ RwIm3DVertexSetU(&vptr[7], 1.0f);
+ RwIm3DVertexSetV(&vptr[7], 1.0f);
+ RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z);
+ RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z);
+ RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z);
+ RwIm3DVertexSetPos(&vptr[3], verts[3].x, verts[3].y, verts[3].z);
+ RwIm3DVertexSetPos(&vptr[4], verts[4].x, verts[4].y, verts[4].z);
+ RwIm3DVertexSetPos(&vptr[5], verts[5].x, verts[5].y, verts[5].z);
+ RwIm3DVertexSetPos(&vptr[6], verts[6].x, verts[6].y, verts[6].z);
+ RwIm3DVertexSetPos(&vptr[7], verts[7].x, verts[7].y, verts[7].z);
+ iptr[0] = 0; iptr[1] = 1; iptr[2] = 2;
+ iptr[3] = 1; iptr[4] = 3; iptr[5] = 2;
+ iptr[6] = 1; iptr[7] = 5; iptr[8] = 7;
+ iptr[9] = 1; iptr[10] = 7; iptr[11] = 3;
+ iptr[12] = 2; iptr[13] = 3; iptr[14] = 7;
+ iptr[15] = 2; iptr[16] = 7; iptr[17] = 6;
+ iptr[18] = 0; iptr[19] = 5; iptr[20] = 1;
+ iptr[21] = 0; iptr[22] = 4; iptr[23] = 5;
+ iptr[24] = 0; iptr[25] = 2; iptr[26] = 4;
+ iptr[27] = 2; iptr[28] = 6; iptr[29] = 4;
+ iptr[30] = 4; iptr[31] = 6; iptr[32] = 7;
+ iptr[33] = 4; iptr[34] = 7; iptr[35] = 5;
+ RenderBuffer::StopStoring();
+ }
+
+ RenderBuffer::RenderStuffInBuffer();
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+}
+
+
+/*
+ * ColModel code
+ */
+
+void
+CColSphere::Set(float radius, const CVector &center, uint8 surf, uint8 piece)
+{
+ this->radius = radius;
+ this->center = center;
+ this->surface = surf;
+ this->piece = piece;
+}
+
+void
+CColBox::Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece)
+{
+ this->min = min;
+ this->max = max;
+ this->surface = surf;
+ this->piece = piece;
+}
+
+void
+CColLine::Set(const CVector &p0, const CVector &p1)
+{
+ this->p0 = p0;
+ this->p1 = p1;
+}
+
+void
+CColTriangle::Set(const CVector *, int a, int b, int c, uint8 surf, uint8 piece)
+{
+ this->a = a;
+ this->b = b;
+ this->c = c;
+ this->surface = surf;
+}
+
+void
+CColTrianglePlane::Set(const CVector *v, CColTriangle &tri)
+{
+ const CVector &va = v[tri.a];
+ const CVector &vb = v[tri.b];
+ const CVector &vc = v[tri.c];
+
+ normal = CrossProduct(vc-va, vb-va);
+ normal.Normalise();
+ dist = DotProduct(normal, va);
+ CVector an(fabs(normal.x), fabs(normal.y), fabs(normal.z));
+ // find out largest component and its direction
+ if(an.x > an.y && an.x > an.z)
+ dir = normal.x < 0.0f ? DIR_X_NEG : DIR_X_POS;
+ else if(an.y > an.z)
+ dir = normal.y < 0.0f ? DIR_Y_NEG : DIR_Y_POS;
+ else
+ dir = normal.z < 0.0f ? DIR_Z_NEG : DIR_Z_POS;
+}
+
+CColModel::CColModel(void)
+{
+ numSpheres = 0;
+ spheres = nil;
+ numLines = 0;
+ lines = nil;
+ numBoxes = 0;
+ boxes = nil;
+ numTriangles = 0;
+ vertices = nil;
+ triangles = nil;
+ trianglePlanes = nil;
+ level = CGame::currLevel;
+ ownsCollisionVolumes = true;
+}
+
+CColModel::~CColModel(void)
+{
+ RemoveCollisionVolumes();
+ RemoveTrianglePlanes();
+}
+
+void
+CColModel::RemoveCollisionVolumes(void)
+{
+ if(ownsCollisionVolumes){
+ RwFree(spheres);
+ RwFree(lines);
+ RwFree(boxes);
+ RwFree(vertices);
+ RwFree(triangles);
+ }
+ numSpheres = 0;
+ numLines = 0;
+ numBoxes = 0;
+ numTriangles = 0;
+ spheres = nil;
+ lines = nil;
+ boxes = nil;
+ vertices = nil;
+ triangles = nil;
+}
+
+void
+CColModel::CalculateTrianglePlanes(void)
+{
+ // HACK: allocate space for one more element to stuff the link pointer into
+ trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1));
+ for(int i = 0; i < numTriangles; i++)
+ trianglePlanes[i].Set(vertices, triangles[i]);
+}
+
+void
+CColModel::RemoveTrianglePlanes(void)
+{
+ RwFree(trianglePlanes);
+ trianglePlanes = nil;
+}
+
+void
+CColModel::SetLinkPtr(CLink<CColModel*> *lptr)
+{
+ assert(trianglePlanes);
+ *(CLink<CColModel*>**)ALIGNPTR(&trianglePlanes[numTriangles]) = lptr;
+}
+
+CLink<CColModel*>*
+CColModel::GetLinkPtr(void)
+{
+ assert(trianglePlanes);
+ return *(CLink<CColModel*>**)ALIGNPTR(&trianglePlanes[numTriangles]);
+}
+
+void
+CColModel::GetTrianglePoint(CVector &v, int i) const
+{
+ v = vertices[i];
+}
+
+CColModel&
+CColModel::operator=(const CColModel &other)
+{
+ int i;
+ int numVerts;
+
+ boundingSphere = other.boundingSphere;
+ boundingBox = other.boundingBox;
+
+ // copy spheres
+ if(other.numSpheres){
+ if(numSpheres != other.numSpheres){
+ numSpheres = other.numSpheres;
+ if(spheres)
+ RwFree(spheres);
+ spheres = (CColSphere*)RwMalloc(numSpheres*sizeof(CColSphere));
+ }
+ for(i = 0; i < numSpheres; i++)
+ spheres[i] = other.spheres[i];
+ }else{
+ numSpheres = 0;
+ if(spheres)
+ RwFree(spheres);
+ spheres = nil;
+ }
+
+ // copy lines
+ if(other.numLines){
+ if(numLines != other.numLines){
+ numLines = other.numLines;
+ if(lines)
+ RwFree(lines);
+ lines = (CColLine*)RwMalloc(numLines*sizeof(CColLine));
+ }
+ for(i = 0; i < numLines; i++)
+ lines[i] = other.lines[i];
+ }else{
+ numLines = 0;
+ if(lines)
+ RwFree(lines);
+ lines = nil;
+ }
+
+ // copy boxes
+ if(other.numBoxes){
+ if(numBoxes != other.numBoxes){
+ numBoxes = other.numBoxes;
+ if(boxes)
+ RwFree(boxes);
+ boxes = (CColBox*)RwMalloc(numBoxes*sizeof(CColBox));
+ }
+ for(i = 0; i < numBoxes; i++)
+ boxes[i] = other.boxes[i];
+ }else{
+ numBoxes = 0;
+ if(boxes)
+ RwFree(boxes);
+ boxes = nil;
+ }
+
+ // copy mesh
+ if(other.numTriangles){
+ // copy vertices
+ numVerts = 0;
+ for(i = 0; i < other.numTriangles; i++){
+ if(other.triangles[i].a > numVerts)
+ other.triangles[i].a = numVerts;
+ if(other.triangles[i].b > numVerts)
+ other.triangles[i].b = numVerts;
+ if(other.triangles[i].c > numVerts)
+ other.triangles[i].c = numVerts;
+ }
+ numVerts++;
+ if(vertices)
+ RwFree(vertices);
+ if(numVerts){
+ vertices = (CVector*)RwMalloc(numVerts*sizeof(CVector));
+ for(i = 0; i < numVerts; i++)
+ vertices[i] = other.vertices[i];
+ }
+
+ // copy triangles
+ if(numTriangles != other.numTriangles){
+ numTriangles = other.numTriangles;
+ if(triangles)
+ RwFree(triangles);
+ triangles = (CColTriangle*)RwMalloc(numTriangles*sizeof(CColTriangle));
+ }
+ for(i = 0; i < numTriangles; i++)
+ triangles[i] = other.triangles[i];
+ }else{
+ numTriangles = 0;
+ if(triangles)
+ RwFree(triangles);
+ triangles = nil;
+ if(vertices)
+ RwFree(vertices);
+ vertices = nil;
+ }
+ return *this;
+}
+
+STARTPATCHES
+ InjectHook(0x4B9C30, (CMatrix& (*)(const CMatrix &src, CMatrix &dst))Invert, PATCH_JUMP);
+
+ InjectHook(0x40B380, CCollision::Init, PATCH_JUMP);
+ InjectHook(0x40B3A0, CCollision::Shutdown, PATCH_JUMP);
+ InjectHook(0x40B3B0, CCollision::Update, PATCH_JUMP);
+ InjectHook(0x40B5B0, CCollision::LoadCollisionWhenINeedIt, PATCH_JUMP);
+ InjectHook(0x40B900, CCollision::SortOutCollisionAfterLoad, PATCH_JUMP);
+
+ InjectHook(0x40BB70, CCollision::TestSphereBox, PATCH_JUMP);
+ InjectHook(0x40E130, CCollision::TestLineBox, PATCH_JUMP);
+ InjectHook(0x40E5C0, CCollision::TestVerticalLineBox, PATCH_JUMP);
+ InjectHook(0x40EC10, CCollision::TestLineTriangle, PATCH_JUMP);
+ InjectHook(0x40DAA0, CCollision::TestLineSphere, PATCH_JUMP);
+ InjectHook(0x40C580, CCollision::TestSphereTriangle, PATCH_JUMP);
+ InjectHook(0x40F720, CCollision::TestLineOfSight, PATCH_JUMP);
+
+ InjectHook(0x40B9F0, CCollision::ProcessSphereSphere, PATCH_JUMP);
+ InjectHook(0x40BC00, CCollision::ProcessSphereBox, PATCH_JUMP);
+ InjectHook(0x40E670, CCollision::ProcessLineBox, PATCH_JUMP);
+ InjectHook(0x40DE80, CCollision::ProcessLineSphere, PATCH_JUMP);
+ InjectHook(0x40FB50, CCollision::ProcessVerticalLineTriangle, PATCH_JUMP);
+ InjectHook(0x40F140, CCollision::ProcessLineTriangle, PATCH_JUMP);
+ InjectHook(0x40CE30, CCollision::ProcessSphereTriangle, PATCH_JUMP);
+
+ InjectHook(0x40F910, CCollision::ProcessLineOfSight, PATCH_JUMP);
+ InjectHook(0x410120, CCollision::ProcessVerticalLine, PATCH_JUMP);
+ InjectHook(0x410BE0, CCollision::ProcessColModels, PATCH_JUMP);
+
+ InjectHook(0x40B960, CCollision::CalculateTrianglePlanes, PATCH_JUMP);
+ InjectHook(0x411640, &CLink<CColModel*>::Remove, PATCH_JUMP);
+ InjectHook(0x411620, &CLink<CColModel*>::Insert, PATCH_JUMP);
+ InjectHook(0x4115C0, &CLinkList<CColModel*>::Insert, PATCH_JUMP);
+ InjectHook(0x411600, &CLinkList<CColModel*>::Remove, PATCH_JUMP);
+// InjectHook(0x411530, &CLinkList<CColModel*>::Init, PATCH_JUMP);
+
+ InjectHook(0x411E40, (void (CColSphere::*)(float, const CVector&, uint8, uint8))&CColSphere::Set, PATCH_JUMP);
+ InjectHook(0x40B2A0, &CColBox::Set, PATCH_JUMP);
+ InjectHook(0x40B320, &CColLine::ctor, PATCH_JUMP);
+ InjectHook(0x40B350, &CColLine::Set, PATCH_JUMP);
+ InjectHook(0x411E70, &CColTriangle::Set, PATCH_JUMP);
+
+ InjectHook(0x411EA0, &CColTrianglePlane::Set, PATCH_JUMP);
+ InjectHook(0x412140, &CColTrianglePlane::GetNormal, PATCH_JUMP);
+
+ InjectHook(0x411680, &CColModel::ctor, PATCH_JUMP);
+ InjectHook(0x4116E0, &CColModel::dtor, PATCH_JUMP);
+ InjectHook(0x411D80, &CColModel::RemoveCollisionVolumes, PATCH_JUMP);
+ InjectHook(0x411CB0, &CColModel::CalculateTrianglePlanes, PATCH_JUMP);
+ InjectHook(0x411D10, &CColModel::RemoveTrianglePlanes, PATCH_JUMP);
+ InjectHook(0x411D40, &CColModel::SetLinkPtr, PATCH_JUMP);
+ InjectHook(0x411D60, &CColModel::GetLinkPtr, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Collision.h b/src/core/Collision.h
new file mode 100644
index 00000000..5a9058d3
--- /dev/null
+++ b/src/core/Collision.h
@@ -0,0 +1,157 @@
+#pragma once
+
+#include "templates.h"
+#include "Game.h" // for eLevelName
+
+struct CColSphere
+{
+ CVector center;
+ float radius;
+ uint8 surface;
+ uint8 piece;
+
+ void Set(float radius, const CVector &center, uint8 surf, uint8 piece);
+ void Set(float radius, const CVector &center) { this->center = center; this->radius = radius; }
+};
+
+struct CColBox
+{
+ CVector min;
+ CVector max;
+ uint8 surface;
+ uint8 piece;
+
+ void Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece);
+ CVector GetSize(void) { return max - min; }
+};
+
+struct CColLine
+{
+ CVector p0;
+ int pad0;
+ CVector p1;
+ int pad1;
+
+ CColLine(void) { };
+ CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; };
+ void Set(const CVector &p0, const CVector &p1);
+
+ CColLine *ctor(CVector *p0, CVector *p1) { return ::new (this) CColLine(*p0, *p1); }
+};
+
+struct CColTriangle
+{
+ uint16 a;
+ uint16 b;
+ uint16 c;
+ uint8 surface;
+
+ void Set(const CVector *v, int a, int b, int c, uint8 surf, uint8 piece);
+};
+
+struct CColTrianglePlane
+{
+ CVector normal;
+ float dist;
+ uint8 dir;
+
+ void Set(const CVector *v, CColTriangle &tri);
+ void GetNormal(CVector &n) const { n = normal; }
+ float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; };
+};
+
+struct CColPoint
+{
+ CVector point;
+ int pad1;
+ // the surface normal on the surface of point
+ CVector normal;
+ int pad2;
+ uint8 surfaceA;
+ uint8 pieceA;
+ uint8 surfaceB;
+ uint8 pieceB;
+ float depth;
+};
+
+struct CStoredCollPoly
+{
+ CVector verts[3];
+ bool valid;
+};
+
+struct CColModel
+{
+ CColSphere boundingSphere;
+ CColBox boundingBox;
+ short numSpheres;
+ short numLines;
+ short numBoxes;
+ short numTriangles;
+ int level;
+ bool ownsCollisionVolumes;
+ CColSphere *spheres;
+ CColLine *lines;
+ CColBox *boxes;
+ CVector *vertices;
+ CColTriangle *triangles;
+ CColTrianglePlane *trianglePlanes;
+
+ CColModel(void);
+ ~CColModel(void);
+ void RemoveCollisionVolumes(void);
+ void CalculateTrianglePlanes(void);
+ void RemoveTrianglePlanes(void);
+ CLink<CColModel*> *GetLinkPtr(void);
+ void SetLinkPtr(CLink<CColModel*>*);
+ void GetTrianglePoint(CVector &v, int i) const;
+
+ CColModel *ctor(void) { return ::new (this) CColModel(); }
+ void dtor(void) { this->CColModel::~CColModel(); }
+ CColModel& operator=(const CColModel& other);
+};
+
+class CCollision
+{
+public:
+ static eLevelName &ms_collisionInMemory;
+ static CLinkList<CColModel*> &ms_colModelCache;
+
+ static void Init(void);
+ static void Shutdown(void);
+ static void Update(void);
+ static void LoadCollisionWhenINeedIt(bool changeLevel);
+ static void SortOutCollisionAfterLoad(void);
+ static void LoadCollisionScreen(eLevelName level);
+ static void DrawColModel(const CMatrix &mat, const CColModel &colModel);
+ static void DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id);
+
+ static void CalculateTrianglePlanes(CColModel *model);
+
+ // all these return true if there's a collision
+ static bool TestSphereSphere(const CColSphere &s1, const CColSphere &s2);
+ static bool TestSphereBox(const CColSphere &sph, const CColBox &box);
+ static bool TestLineBox(const CColLine &line, const CColBox &box);
+ static bool TestVerticalLineBox(const CColLine &line, const CColBox &box);
+ static bool TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane);
+ static bool TestLineSphere(const CColLine &line, const CColSphere &sph);
+ static bool TestSphereTriangle(const CColSphere &sphere, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane);
+ static bool TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough);
+
+ static bool ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq);
+ static bool ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq);
+ static bool ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist);
+ static bool ProcessVerticalLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist, CStoredCollPoly *poly);
+ static bool ProcessLineTriangle(const CColLine &line , const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist);
+ static bool ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist);
+ static bool ProcessSphereTriangle(const CColSphere &sph, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindistsq);
+ static bool ProcessLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough);
+ static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly);
+ static int32 ProcessColModels(const CMatrix &matrix1, CColModel &model1, const CMatrix &matrix2, CColModel &model2, CColPoint *point1, CColPoint *point2, float *linedists);
+
+ // TODO:
+ // CCollision::IsStoredPolyStillValidVerticalLine
+
+ static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point);
+ static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest);
+};
diff --git a/src/core/ControllerConfig.cpp b/src/core/ControllerConfig.cpp
new file mode 100644
index 00000000..d7567ac4
--- /dev/null
+++ b/src/core/ControllerConfig.cpp
@@ -0,0 +1,57 @@
+ #define DIRECTINPUT_VERSION 0x0800
+ #include <dinput.h>
+#include "common.h"
+#include "patcher.h"
+#include "ControllerConfig.h"
+#include "Pad.h"
+#include "FileMgr.h"
+
+CControllerConfigManager &ControlsManager = *(CControllerConfigManager*)0x8F43A4;
+
+WRAPPER void CControllerConfigManager::UpdateJoyButtonState(int padnumber) { EAXJMP(0x58F5B0); }
+WRAPPER void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonDown(int button, int padnumber) { EAXJMP(0x58C5E0); }
+WRAPPER void CControllerConfigManager::AffectControllerStateOn_ButtonDown(int button, eControllerType type) { EAXJMP(0x58C730); }
+WRAPPER void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonUp(int button, int padnumber) { EAXJMP(0x58CE80); }
+WRAPPER void CControllerConfigManager::AffectControllerStateOn_ButtonUp(int button, int padnumber) { EAXJMP(0x58CFD0); }
+WRAPPER void CControllerConfigManager::MakeControllerActionsBlank() { EAXJMP(0x58B7A0); }
+WRAPPER void CControllerConfigManager::InitDefaultControlConfiguration() { EAXJMP(0x58B930); }
+WRAPPER void CControllerConfigManager::InitDefaultControlConfigMouse(CMouseControllerState const &mousestate) { EAXJMP(0x58BD00); }
+WRAPPER int32 CControllerConfigManager::GetJoyButtonJustDown() { EAXJMP(0x58B7D0); }
+WRAPPER void CControllerConfigManager::InitDefaultControlConfigJoyPad(unsigned int buttons) { EAXJMP(0x58BD90); }
+WRAPPER void CControllerConfigManager::ClearSimButtonPressCheckers() { EAXJMP(0x58D220); }
+WRAPPER void CControllerConfigManager::AffectPadFromKeyBoard() { EAXJMP(0x58D0C0); }
+WRAPPER void CControllerConfigManager::AffectPadFromMouse() { EAXJMP(0x58D1A0); }
+
+void CControllerConfigManager::LoadSettings(int32 file)
+{
+ bool bValid = true;
+
+ if ( file )
+ {
+ char buff[29];
+ CFileMgr::Read(file, buff, sizeof(buff));
+
+ if ( !strncmp(buff, "THIS FILE IS NOT VALID YET", sizeof(buff) - 3) )
+ bValid = false;
+ else
+ CFileMgr::Seek(file, 0, 0);
+ }
+
+ if ( bValid )
+ {
+ ControlsManager.MakeControllerActionsBlank();
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ for ( int j = 0; j < 41; j++ )
+ {
+ CFileMgr::Read(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind));
+ }
+ }
+ }
+}
+
+WRAPPER void CControllerConfigManager::SaveSettings(int32 file)
+{
+ EAXJMP(0x58B800);
+}
diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h
new file mode 100644
index 00000000..581efe05
--- /dev/null
+++ b/src/core/ControllerConfig.h
@@ -0,0 +1,58 @@
+#pragma once
+
+
+// based on x-gtasa
+
+enum eControllerType
+{
+ KEYBOARD,
+ OPTIONAL_EXTRA,
+ MOUSE,
+ JOYSTICK,
+};
+
+class CMouseControllerState;
+
+class CControllerConfigManager
+{
+public:
+ struct tControllerConfigBind
+ {
+ RsKeyCodes m_Key;
+ int32 m_ContSetOrder;
+ };
+
+ bool field_0;
+ char _pad0[3];
+ DIJOYSTATE2 m_OldState;
+ DIJOYSTATE2 m_NewState;
+ wchar m_aActionNames[41][40];
+ bool m_aButtonStates[17];
+ char _pad1[3];
+ tControllerConfigBind m_aSettings[41][4];
+ uint8 m_aSimCheckers[4][4];
+ bool m_bMouseAssociated;
+ char _pad2[3];
+
+ void UpdateJoyButtonState(int padnumber);
+ void UpdateJoyInConfigMenus_ButtonDown(int button, int padnumber);
+ void AffectControllerStateOn_ButtonDown(int button, eControllerType type);
+ void UpdateJoyInConfigMenus_ButtonUp(int button, int padnumber);
+ void AffectControllerStateOn_ButtonUp(int button, int padnumber);
+
+ int32 GetJoyButtonJustDown();
+ void LoadSettings(int32 file);
+ void SaveSettings(int32 file);
+ void MakeControllerActionsBlank();
+ void InitDefaultControlConfiguration();
+ void InitDefaultControlConfigMouse(CMouseControllerState const &mousestate);
+ void InitDefaultControlConfigJoyPad(unsigned int buttons);
+ void ClearSimButtonPressCheckers();
+ void AffectPadFromKeyBoard();
+ void AffectPadFromMouse();
+
+};
+
+VALIDATE_SIZE(CControllerConfigManager, 0x143C);
+
+extern CControllerConfigManager &ControlsManager; \ No newline at end of file
diff --git a/src/core/CutsceneMgr.cpp b/src/core/CutsceneMgr.cpp
new file mode 100644
index 00000000..744ef53d
--- /dev/null
+++ b/src/core/CutsceneMgr.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "patcher.h"
+#include "CutsceneMgr.h"
+
+bool &CCutsceneMgr::ms_running = *(bool*)0x95CCF5;
+bool &CCutsceneMgr::ms_cutsceneProcessing = *(bool*)0x95CD9F;
+CDirectory *&CCutsceneMgr::ms_pCutsceneDir = *(CDirectory**)0x8F5F88;
diff --git a/src/core/CutsceneMgr.h b/src/core/CutsceneMgr.h
new file mode 100644
index 00000000..89f6ab8d
--- /dev/null
+++ b/src/core/CutsceneMgr.h
@@ -0,0 +1,15 @@
+#pragma once
+
+class CDirectory;
+
+class CCutsceneMgr
+{
+ static bool &ms_running;
+ static bool &ms_cutsceneProcessing;
+
+public:
+ static CDirectory *&ms_pCutsceneDir;
+
+ static bool IsRunning(void) { return ms_running; }
+ static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; }
+};
diff --git a/src/core/Directory.cpp b/src/core/Directory.cpp
new file mode 100644
index 00000000..3e0d5382
--- /dev/null
+++ b/src/core/Directory.cpp
@@ -0,0 +1,65 @@
+#include "common.h"
+#include "patcher.h"
+#include "FileMgr.h"
+#include "Directory.h"
+
+CDirectory::CDirectory(int32 maxEntries)
+ : numEntries(0), maxEntries(maxEntries)
+{
+ entries = new DirectoryInfo[maxEntries];
+}
+
+CDirectory::~CDirectory(void)
+{
+ delete[] entries;
+}
+
+void
+CDirectory::ReadDirFile(const char *filename)
+{
+ int fd;
+ DirectoryInfo dirinfo;
+
+ fd = CFileMgr::OpenFile(filename, "rb");
+ while(CFileMgr::Read(fd, (char*)&dirinfo, sizeof(dirinfo)))
+ AddItem(dirinfo);
+ CFileMgr::CloseFile(fd);
+}
+
+bool
+CDirectory::WriteDirFile(const char *filename)
+{
+ int fd, n;
+ fd = CFileMgr::OpenFileForWriting(filename);
+ n = CFileMgr::Write(fd, (char*)entries, numEntries*sizeof(DirectoryInfo));
+ CFileMgr::CloseFile(fd);
+ return n == numEntries*sizeof(DirectoryInfo);
+}
+
+void
+CDirectory::AddItem(const DirectoryInfo &dirinfo)
+{
+ assert(numEntries < maxEntries);
+ entries[numEntries++] = dirinfo;
+}
+
+bool
+CDirectory::FindItem(const char *name, uint32 &offset, uint32 &size)
+{
+ int i;
+
+ for(i = 0; i < numEntries; i++)
+ if(strcmpi(entries[i].name, name) == 0){
+ offset = entries[i].offset;
+ size = entries[i].size;
+ return true;
+ }
+ return false;
+}
+
+STARTPATCHES
+ InjectHook(0x473630, &CDirectory::ReadDirFile, PATCH_JUMP);
+ InjectHook(0x473690, &CDirectory::WriteDirFile, PATCH_JUMP);
+ InjectHook(0x473600, &CDirectory::AddItem, PATCH_JUMP);
+ InjectHook(0x4736E0, &CDirectory::FindItem, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Directory.h b/src/core/Directory.h
new file mode 100644
index 00000000..06e6bba4
--- /dev/null
+++ b/src/core/Directory.h
@@ -0,0 +1,22 @@
+#pragma once
+
+class CDirectory
+{
+public:
+ struct DirectoryInfo {
+ uint32 offset;
+ uint32 size;
+ char name[24];
+ };
+ DirectoryInfo *entries;
+ int32 maxEntries;
+ int32 numEntries;
+
+ CDirectory(int32 maxEntries);
+ ~CDirectory(void);
+
+ void ReadDirFile(const char *filename);
+ bool WriteDirFile(const char *filename);
+ void AddItem(const DirectoryInfo &dirinfo);
+ bool FindItem(const char *name, uint32 &offset, uint32 &size);
+};
diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp
new file mode 100644
index 00000000..fdc3b9d7
--- /dev/null
+++ b/src/core/FileLoader.cpp
@@ -0,0 +1,1182 @@
+#include "common.h"
+#include "main.h"
+#include "patcher.h"
+#include "math/Quaternion.h"
+#include "ModelInfo.h"
+#include "ModelIndices.h"
+#include "TempColModels.h"
+#include "VisibilityPlugins.h"
+#include "FileMgr.h"
+#include "HandlingMgr.h"
+#include "CarCtrl.h"
+#include "PedType.h"
+#include "PedStats.h"
+#include "AnimManager.h"
+#include "Game.h"
+#include "RwHelper.h"
+#include "NodeName.h"
+#include "TxdStore.h"
+#include "PathFind.h"
+#include "ObjectData.h"
+#include "DummyObject.h"
+#include "World.h"
+#include "Zones.h"
+#include "ZoneCull.h"
+#include "CdStream.h"
+#include "FileLoader.h"
+
+char CFileLoader::ms_line[256];
+
+const char*
+GetFilename(const char *filename)
+{
+ char *s = strrchr((char*)filename, '\\');
+ return s ? s+1 : filename;
+}
+
+void
+LoadingScreenLoadingFile(const char *filename)
+{
+ sprintf(gString, "Loading %s", GetFilename(filename));
+ LoadingScreen("Loading the Game", gString, nil);
+}
+
+void
+CFileLoader::LoadLevel(const char *filename)
+{
+ int fd;
+ RwTexDictionary *savedTxd;
+ eLevelName savedLevel;
+ bool objectsLoaded;
+ char *line;
+ char txdname[64];
+
+ savedTxd = RwTexDictionaryGetCurrent();
+ objectsLoaded = false;
+ savedLevel = CGame::currLevel;
+ if(savedTxd == nil){
+ savedTxd = RwTexDictionaryCreate();
+ RwTexDictionarySetCurrent(savedTxd);
+ }
+ fd = CFileMgr::OpenFile(filename, "r");
+ assert(fd > 0);
+
+ for(line = LoadLine(fd); line; line = LoadLine(fd)){
+ if(*line == '#')
+ continue;
+
+ if(strncmp(line, "EXIT", 9) == 0) // BUG: 9?
+ break;
+
+ if(strncmp(line, "IMAGEPATH", 9) == 0){
+ RwImageSetPath(line + 10);
+ }else if(strncmp(line, "TEXDICTION", 10) == 0){
+ strcpy(txdname, line+11);
+ LoadingScreenLoadingFile(txdname);
+ RwTexDictionary *txd = LoadTexDictionary(txdname);
+ AddTexDictionaries(savedTxd, txd);
+ RwTexDictionaryDestroy(txd);
+ }else if(strncmp(line, "COLFILE", 7) == 0){
+ int level;
+ sscanf(line+8, "%d", &level);
+ CGame::currLevel = (eLevelName)level;
+ LoadingScreenLoadingFile(line+10);
+ LoadCollisionFile(line+10);
+ CGame::currLevel = savedLevel;
+ }else if(strncmp(line, "MODELFILE", 9) == 0){
+ LoadingScreenLoadingFile(line + 10);
+ LoadModelFile(line + 10);
+ }else if(strncmp(line, "HIERFILE", 8) == 0){
+ LoadingScreenLoadingFile(line + 9);
+ LoadClumpFile(line + 9);
+ }else if(strncmp(line, "IDE", 3) == 0){
+ LoadingScreenLoadingFile(line + 4);
+ LoadObjectTypes(line + 4);
+ }else if(strncmp(line, "IPL", 3) == 0){
+ if(!objectsLoaded){
+ // CModelInfo::ConstructMloClumps();
+ CObjectData::Initialise("DATA\\OBJECT.DAT");
+ objectsLoaded = true;
+ }
+ LoadingScreenLoadingFile(line + 4);
+ LoadScene(line + 4);
+ }else if(strncmp(line, "MAPZONE", 7) == 0){
+ LoadingScreenLoadingFile(line + 8);
+ LoadMapZones(line + 8);
+ }else if(strncmp(line, "SPLASH", 6) == 0){
+ LoadSplash(GetRandomSplashScreen());
+ }else if(strncmp(line, "CDIMAGE", 7) == 0){
+ CdStreamAddImage(line + 8);
+ }
+ }
+
+ CFileMgr::CloseFile(fd);
+ RwTexDictionarySetCurrent(savedTxd);
+}
+
+void
+CFileLoader::LoadCollisionFromDatFile(int currlevel)
+{
+ int fd;
+ char *line;
+
+ fd = CFileMgr::OpenFile(CGame::aDatFile, "r");
+ assert(fd > 0);
+
+ for(line = LoadLine(fd); line; line = LoadLine(fd)){
+ if(*line == '#')
+ continue;
+
+ if(strncmp(line, "COLFILE", 7) == 0){
+ int level;
+ sscanf(line+8, "%d", &level);
+ if(currlevel == level)
+ LoadCollisionFile(line+10);
+ }
+ }
+
+ CFileMgr::CloseFile(fd);
+}
+
+char*
+CFileLoader::LoadLine(int fd)
+{
+ int i;
+ char *line;
+
+ if(CFileMgr::ReadLine(fd, ms_line, 256) == false)
+ return nil;
+ for(i = 0; ms_line[i] != '\0'; i++)
+ if(ms_line[i] < ' ' || ms_line[i] == ',')
+ ms_line[i] = ' ';
+ for(line = ms_line; *line <= ' ' && *line != '\0'; line++);
+ return line;
+}
+
+RwTexDictionary*
+CFileLoader::LoadTexDictionary(const char *filename)
+{
+ RwTexDictionary *txd;
+ RwStream *stream;
+
+ txd = nil;
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ debug("Loading texture dictionary file %s\n", filename);
+ if(stream){
+ if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil))
+ txd = RwTexDictionaryGtaStreamRead(stream);
+ RwStreamClose(stream, nil);
+ }
+ if(txd == nil)
+ txd = RwTexDictionaryCreate();
+ return txd;
+}
+
+void
+CFileLoader::LoadCollisionFile(const char *filename)
+{
+ int fd;
+ char modelname[24];
+ CBaseModelInfo *mi;
+ struct {
+ char ident[4];
+ uint32 size;
+ } header;
+
+ debug("Loading collision file %s\n", filename);
+ fd = CFileMgr::OpenFile(filename, "rb");
+
+ while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){
+ assert(strncmp(header.ident, "COLL", 4) == 0);
+ CFileMgr::Read(fd, (char*)work_buff, header.size);
+ memcpy(modelname, work_buff, 24);
+
+ mi = CModelInfo::GetModelInfo(modelname, nil);
+ if(mi){
+ if(mi->GetColModel()){
+ LoadCollisionModel(work_buff+24, *mi->GetColModel(), modelname);
+ }else{
+ CColModel *model = new CColModel;
+ LoadCollisionModel(work_buff+24, *model, modelname);
+ mi->SetColModel(model, true);
+ }
+ }else{
+ debug("colmodel %s can't find a modelinfo\n", modelname);
+ }
+ }
+
+ CFileMgr::CloseFile(fd);
+}
+
+void
+CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname)
+{
+ int i;
+
+ model.boundingSphere.radius = *(float*)(buf);
+ model.boundingSphere.center.x = *(float*)(buf+4);
+ model.boundingSphere.center.y = *(float*)(buf+8);
+ model.boundingSphere.center.z = *(float*)(buf+12);
+ model.boundingBox.min.x = *(float*)(buf+16);
+ model.boundingBox.min.y = *(float*)(buf+20);
+ model.boundingBox.min.z = *(float*)(buf+24);
+ model.boundingBox.max.x = *(float*)(buf+28);
+ model.boundingBox.max.y = *(float*)(buf+32);
+ model.boundingBox.max.z = *(float*)(buf+36);
+ model.numSpheres = *(int16*)(buf+40);
+ buf += 44;
+ if(model.numSpheres > 0){
+ model.spheres = (CColSphere*)RwMalloc(model.numSpheres*sizeof(CColSphere));
+ for(i = 0; i < model.numSpheres; i++){
+ model.spheres[i].Set(*(float*)buf, *(CVector*)(buf+4), buf[16], buf[17]);
+ buf += 20;
+ }
+ }else
+ model.spheres = nil;
+
+ model.numLines = *(int16*)buf;
+ buf += 4;
+ if(model.numLines > 0){
+ model.lines = (CColLine*)RwMalloc(model.numLines*sizeof(CColLine));
+ for(i = 0; i < model.numLines; i++){
+ model.lines[i].Set(*(CVector*)buf, *(CVector*)(buf+12));
+ buf += 24;
+ }
+ }else
+ model.lines = nil;
+
+ model.numBoxes = *(int16*)buf;
+ buf += 4;
+ if(model.numBoxes > 0){
+ model.boxes = (CColBox*)RwMalloc(model.numBoxes*sizeof(CColBox));
+ for(i = 0; i < model.numBoxes; i++){
+ model.boxes[i].Set(*(CVector*)buf, *(CVector*)(buf+12), buf[24], buf[25]);
+ buf += 28;
+ }
+ }else
+ model.boxes = nil;
+
+ int32 numVertices = *(int16*)buf;
+ buf += 4;
+ if(numVertices > 0){
+ model.vertices = (CVector*)RwMalloc(numVertices*sizeof(CVector));
+ for(i = 0; i < numVertices; i++){
+ model.vertices[i] = *(CVector*)buf;
+ if(fabs(model.vertices[i].x) >= 256.0f ||
+ fabs(model.vertices[i].y) >= 256.0f ||
+ fabs(model.vertices[i].z) >= 256.0f)
+ printf("%s:Collision volume too big\n", modelname);
+ buf += 12;
+ }
+ }else
+ model.vertices = nil;
+
+ model.numTriangles = *(int16*)buf;
+ buf += 4;
+ if(model.numTriangles > 0){
+ model.triangles = (CColTriangle*)RwMalloc(model.numTriangles*sizeof(CColTriangle));
+ for(i = 0; i < model.numTriangles; i++){
+ model.triangles[i].Set(model.vertices, *(int32*)buf, *(int32*)(buf+4), *(int32*)(buf+8), buf[12], buf[13]);
+ buf += 16;
+ }
+ }else
+ model.triangles = nil;
+}
+
+static void
+GetNameAndLOD(char *nodename, char *name, int *n)
+{
+ char *underscore = nil;
+ for(char *s = nodename; *s != '\0'; s++){
+ if(s[0] == '_' && (s[1] == 'l' || s[1] == 'L'))
+ underscore = s;
+ }
+ if(underscore){
+ strncpy(name, nodename, underscore - nodename);
+ name[underscore - nodename] = '\0';
+ *n = atoi(underscore + 2);
+ }else{
+ strncpy(name, nodename, 24);
+ *n = 0;
+ }
+}
+
+RpAtomic*
+CFileLoader::FindRelatedModelInfoCB(RpAtomic *atomic, void *data)
+{
+ CSimpleModelInfo *mi;
+ char *nodename, name[24];
+ int n;
+ RpClump *clump = (RpClump*)data;
+
+ nodename = GetFrameNodeName(RpClumpGetFrame(atomic));
+ GetNameAndLOD(nodename, name, &n);
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(name, nil);
+ if(mi){
+ assert(mi->IsSimple());
+ mi->SetAtomic(n, atomic);
+ RpClumpRemoveAtomic(clump, atomic);
+ RpAtomicSetFrame(atomic, RwFrameCreate());
+ CVisibilityPlugins::SetAtomicModelInfo(atomic, mi);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ }else{
+ debug("Can't find Atomic %s\n", name);
+ }
+
+ return atomic;
+}
+
+void
+CFileLoader::LoadModelFile(const char *filename)
+{
+ RwStream *stream;
+ RpClump *clump;
+
+ debug("Loading model file %s\n", filename);
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
+ clump = RpClumpStreamRead(stream);
+ if(clump){
+ RpClumpForAllAtomics(clump, FindRelatedModelInfoCB, clump);
+ RpClumpDestroy(clump);
+ }
+ }
+ RwStreamClose(stream, nil);
+}
+
+void
+CFileLoader::LoadClumpFile(const char *filename)
+{
+ RwStream *stream;
+ RpClump *clump;
+ char *nodename, name[24];
+ int n;
+ CClumpModelInfo *mi;
+
+ debug("Loading model file %s\n", filename);
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ while(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
+ clump = RpClumpStreamRead(stream);
+ if(clump){
+ nodename = GetFrameNodeName(RpClumpGetFrame(clump));
+ GetNameAndLOD(nodename, name, &n);
+ mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(name, nil);
+ assert(mi->IsClump());
+ if(mi)
+ mi->SetClump(clump);
+ else
+ RpClumpDestroy(clump);
+ }
+ }
+ RwStreamClose(stream, nil);
+}
+
+bool
+CFileLoader::LoadClumpFile(RwStream *stream, uint32 id)
+{
+ RpClump *clump;
+ CClumpModelInfo *mi;
+
+ if(!RwStreamFindChunk(stream, rwID_CLUMP, nil, nil))
+ return false;
+ clump = RpClumpStreamRead(stream);
+ if(clump == nil)
+ return false;
+ mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id);
+ mi->SetClump(clump);
+ if(mi->m_type == MITYPE_PED && id != 0 && RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
+ // Read LOD ped
+ clump = RpClumpStreamRead(stream);
+ if(clump){
+ ((CPedModelInfo*)mi)->SetLowDetailClump(clump);
+ RpClumpDestroy(clump);
+ }
+ }
+ return true;
+}
+
+bool
+CFileLoader::StartLoadClumpFile(RwStream *stream, uint32 id)
+{
+ if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
+ printf("Start loading %s\n", CModelInfo::GetModelInfo(id)->GetName());
+ return RpClumpGtaStreamRead1(stream);
+ }else{
+ printf("FAILED\n");
+ return false;
+ }
+}
+
+bool
+CFileLoader::FinishLoadClumpFile(RwStream *stream, uint32 id)
+{
+ RpClump *clump;
+ CClumpModelInfo *mi;
+
+ printf("Finish loading %s\n", CModelInfo::GetModelInfo(id)->GetName());
+ clump = RpClumpGtaStreamRead2(stream);
+
+ if(clump){
+ mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id);
+ mi->SetClump(clump);
+ return true;
+ }else{
+ printf("FAILED\n");
+ return false;
+ }
+}
+
+CSimpleModelInfo *gpRelatedModelInfo;
+
+bool
+CFileLoader::LoadAtomicFile(RwStream *stream, uint32 id)
+{
+ RpClump *clump;
+
+ if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
+ clump = RpClumpStreamRead(stream);
+ if(clump == nil)
+ return false;
+ gpRelatedModelInfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id);
+ RpClumpForAllAtomics(clump, SetRelatedModelInfoCB, clump);
+ RpClumpDestroy(clump);
+ }
+ return true;
+}
+
+RpAtomic*
+CFileLoader::SetRelatedModelInfoCB(RpAtomic *atomic, void *data)
+{
+ char *nodename, name[24];
+ int n;
+ RpClump *clump = (RpClump*)data;
+
+ nodename = GetFrameNodeName(RpAtomicGetFrame(atomic));
+ GetNameAndLOD(nodename, name, &n);
+ gpRelatedModelInfo->SetAtomic(n, atomic);
+ RpClumpRemoveAtomic(clump, atomic);
+ RpAtomicSetFrame(atomic, RwFrameCreate());
+ CVisibilityPlugins::SetAtomicModelInfo(atomic, gpRelatedModelInfo);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ return atomic;
+}
+
+RpClump*
+CFileLoader::LoadAtomicFile2Return(const char *filename)
+{
+ RwStream *stream;
+ RpClump *clump;
+
+ clump = nil;
+ debug("Loading model file %s\n", filename);
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil))
+ clump = RpClumpStreamRead(stream);
+ RwStreamClose(stream, nil);
+ return clump;
+}
+
+static RwTexture*
+MoveTexturesCB(RwTexture *texture, void *pData)
+{
+ RwTexDictionaryAddTexture((RwTexDictionary*)pData, texture);
+ return texture;
+}
+
+void
+CFileLoader::AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src)
+{
+ RwTexDictionaryForAllTextures(src, MoveTexturesCB, dst);
+}
+
+void
+CFileLoader::LoadObjectTypes(const char *filename)
+{
+ enum {
+ NONE,
+ OBJS,
+ MLO,
+ TOBJ,
+ HIER,
+ CARS,
+ PEDS,
+ PATH,
+ TWODFX
+ };
+ char *line;
+ int fd;
+ int section;
+ int pathIndex;
+ char pathTypeStr[20];
+ int id, pathType;
+// int mlo;
+
+ section = NONE;
+ pathIndex = -1;
+// mlo = 0;
+ debug("Loading object types from %s...\n", filename);
+
+ fd = CFileMgr::OpenFile(filename, "rb");
+ for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){
+ if(*line == '\0' || *line == '#')
+ continue;
+
+ if(section == NONE){
+ if(strncmp(line, "objs", 4) == 0) section = OBJS;
+ else if(strncmp(line, "tobj", 4) == 0) section = TOBJ;
+ else if(strncmp(line, "hier", 4) == 0) section = HIER;
+ else if(strncmp(line, "cars", 4) == 0) section = CARS;
+ else if(strncmp(line, "peds", 4) == 0) section = PEDS;
+ else if(strncmp(line, "path", 4) == 0) section = PATH;
+ else if(strncmp(line, "2dfx", 4) == 0) section = TWODFX;
+ }else if(strncmp(line, "end", 3) == 0){
+ section = section == MLO ? OBJS : NONE;
+ }else switch(section){
+ case OBJS:
+ if(strncmp(line, "sta", 3) == 0)
+ assert(0); // LoadMLO
+ else
+ LoadObject(line);
+ break;
+ case MLO:
+ assert(0); // LoadMLOInstance
+ break;
+ case TOBJ:
+ LoadTimeObject(line);
+ break;
+ case HIER:
+ LoadClumpObject(line);
+ break;
+ case CARS:
+ LoadVehicleObject(line);
+ break;
+ case PEDS:
+ LoadPedObject(line);
+ break;
+ case PATH:
+ if(pathIndex == -1){
+ id = LoadPathHeader(line, pathTypeStr);
+ if(strncmp(pathTypeStr, "ped", 4) == 0)
+ pathType = 1;
+ else if(strncmp(pathTypeStr, "car", 4) == 0)
+ pathType = 0;
+ pathIndex = 0;
+ }else{
+ if(pathType == 1)
+ LoadPedPathNode(line, id, pathIndex);
+ else if(pathType == 0)
+ LoadCarPathNode(line, id, pathIndex);
+ pathIndex++;
+ if(pathIndex == 12)
+ pathIndex = -1;
+ }
+ break;
+ case TWODFX:
+ Load2dEffect(line);
+ break;
+ }
+ }
+ CFileMgr::CloseFile(fd);
+
+ for(id = 0; id < MODELINFOSIZE; id++){
+ CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id);
+ if(mi && mi->IsSimple())
+ mi->SetupBigBuilding();
+ }
+}
+
+void
+SetModelInfoFlags(CSimpleModelInfo *mi, uint32 flags)
+{
+ mi->m_normalCull = !!(flags & 1);
+ mi->m_noFade = !!(flags & 2);
+ mi->m_drawLast = !!(flags & (4|8));
+ mi->m_additive = !!(flags & 8);
+ mi->m_isSubway = !!(flags & 0x10);
+ mi->m_ignoreLight = !!(flags & 0x20);
+ mi->m_noZwrite = !!(flags & 0x40);
+}
+
+void
+CFileLoader::LoadObject(const char *line)
+{
+ int id, numObjs;
+ char model[24], txd[24];
+ float dist[3];
+ uint32 flags;
+ int damaged;
+ CSimpleModelInfo *mi;
+
+ if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4)
+ return;
+
+ switch(numObjs){
+ case 1:
+ sscanf(line, "%d %s %s %d %f %d",
+ &id, model, txd, &numObjs, &dist[0], &flags);
+ damaged = 0;
+ break;
+ case 2:
+ sscanf(line, "%d %s %s %d %f %f %d",
+ &id, model, txd, &numObjs, &dist[0], &dist[1], &flags);
+ damaged = dist[0] < dist[1] ? // Are distances increasing?
+ 0 : // Yes, no damage model
+ 1; // No, 1 is damaged
+ break;
+ case 3:
+ sscanf(line, "%d %s %s %d %f %f %f %d",
+ &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags);
+ damaged = dist[0] < dist[1] ? // Are distances increasing?
+ (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model
+ 1; // No, 1 and 2 are damaged
+ break;
+ }
+
+ mi = CModelInfo::AddSimpleModel(id);
+ mi->SetName(model);
+ mi->SetNumAtomics(numObjs);
+ mi->SetLodDistances(dist);
+ SetModelInfoFlags(mi, flags);
+ mi->m_firstDamaged = damaged;
+ mi->SetTexDictionary(txd);
+ MatchModelString(model, id);
+}
+
+void
+CFileLoader::LoadTimeObject(const char *line)
+{
+ int id, numObjs;
+ char model[24], txd[24];
+ float dist[3];
+ uint32 flags;
+ int timeOn, timeOff;
+ int damaged;
+ CTimeModelInfo *mi, *other;
+
+ if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4)
+ return;
+
+ switch(numObjs){
+ case 1:
+ sscanf(line, "%d %s %s %d %f %d %d %d",
+ &id, model, txd, &numObjs, &dist[0], &flags, &timeOn, &timeOff);
+ damaged = 0;
+ break;
+ case 2:
+ sscanf(line, "%d %s %s %d %f %f %d %d %d",
+ &id, model, txd, &numObjs, &dist[0], &dist[1], &flags, &timeOn, &timeOff);
+ damaged = dist[0] < dist[1] ? // Are distances increasing?
+ 0 : // Yes, no damage model
+ 1; // No, 1 is damaged
+ break;
+ case 3:
+ sscanf(line, "%d %s %s %d %f %f %f %d %d %d",
+ &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags, &timeOn, &timeOff);
+ damaged = dist[0] < dist[1] ? // Are distances increasing?
+ (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model
+ 1; // No, 1 and 2 are damaged
+ break;
+ }
+
+ mi = CModelInfo::AddTimeModel(id);
+ mi->SetName(model);
+ mi->SetNumAtomics(numObjs);
+ mi->SetLodDistances(dist);
+ SetModelInfoFlags(mi, flags);
+ mi->m_firstDamaged = damaged;
+ mi->SetTimes(timeOn, timeOff);
+ mi->SetTexDictionary(txd);
+ other = mi->FindOtherTimeModel();
+ if(other)
+ other->SetOtherTimeModel(id);
+ MatchModelString(model, id);
+}
+
+void
+CFileLoader::LoadClumpObject(const char *line)
+{
+ int id;
+ char model[24], txd[24];
+ CClumpModelInfo *mi;
+
+ if(sscanf(line, "%d %s %s", &id, &model, &txd) == 3){
+ mi = CModelInfo::AddClumpModel(id);
+ mi->SetName(model);
+ mi->SetTexDictionary(txd);
+ mi->SetColModel(&CTempColModels::ms_colModelBBox);
+ }
+}
+
+void
+CFileLoader::LoadVehicleObject(const char *line)
+{
+ int id;
+ char model[24], txd[24];
+ char type[8], handlingId[16], gamename[32], vehclass[12];
+ uint32 frequency, comprules;
+ int32 level, misc;
+ float wheelScale;
+ CVehicleModelInfo *mi;
+ char *p;
+
+ sscanf(line, "%d %s %s %s %s %s %s %d %d %x %d %f",
+ &id, model, txd,
+ type, handlingId, gamename, vehclass,
+ &frequency, &level, &comprules, &misc, &wheelScale);
+
+ mi = CModelInfo::AddVehicleModel(id);
+ mi->SetName(model);
+ mi->SetTexDictionary(txd);
+ for(p = gamename; *p; p++)
+ if(*p == '_') *p = ' ';
+ strncpy(mi->m_gameName, gamename, 32);
+ mi->m_level = level;
+ mi->m_compRules = comprules;
+
+ if(strncmp(type, "car", 4) == 0){
+ mi->m_wheelId = misc;
+ mi->m_wheelScale = wheelScale;
+ mi->m_vehicleType = VEHICLE_TYPE_CAR;
+ }else if(strncmp(type, "boat", 5) == 0){
+ mi->m_vehicleType = VEHICLE_TYPE_BOAT;
+ }else if(strncmp(type, "train", 6) == 0){
+ mi->m_vehicleType = VEHICLE_TYPE_TRAIN;
+ }else if(strncmp(type, "heli", 5) == 0){
+ mi->m_vehicleType = VEHICLE_TYPE_HELI;
+ }else if(strncmp(type, "plane", 6) == 0){
+ mi->m_wheelId = misc;
+ mi->m_wheelScale = 1.0f;
+ mi->m_vehicleType = VEHICLE_TYPE_PLANE;
+ }else if(strncmp(type, "bike", 5) == 0){
+ mi->m_bikeSteerAngle = misc;
+ mi->m_wheelScale = wheelScale;
+ mi->m_vehicleType = VEHICLE_TYPE_BIKE;
+ }else
+ assert(0);
+
+ mi->m_handlingId = mod_HandlingManager.GetHandlingId(handlingId);
+
+ // Well this is kinda dumb....
+ if(strncmp(vehclass, "poorfamily", 11) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_POOR;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_POOR);
+ }else if(strncmp(vehclass, "richfamily", 11) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_RICH;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_RICH);
+ }else if(strncmp(vehclass, "executive", 10) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_EXECUTIVE;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_EXECUTIVE);
+ }else if(strncmp(vehclass, "worker", 7) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_WORKER;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_WORKER);
+ }else if(strncmp(vehclass, "special", 8) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_SPECIAL;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_SPECIAL);
+ }else if(strncmp(vehclass, "big", 4) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_BIG;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_BIG);
+ }else if(strncmp(vehclass, "taxi", 5) == 0){
+ mi->m_vehicleClass = VEHICLE_CLASS_TAXI;
+ while(frequency-- > 0)
+ CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_TAXI);
+ }
+}
+
+void
+CFileLoader::LoadPedObject(const char *line)
+{
+ int id;
+ char model[24], txd[24];
+ char pedType[24], pedStats[24], animGroup[24];
+ int carsCanDrive;
+ CPedModelInfo *mi;
+ int animGroupId;
+
+ if(sscanf(line, "%d %s %s %s %s %s %x",
+ &id, model, txd,
+ pedType, pedStats, animGroup, &carsCanDrive) != 7)
+ return;
+
+ mi = CModelInfo::AddPedModel(id);
+ mi->SetName(model);
+ mi->SetTexDictionary(txd);
+ mi->SetColModel(&CTempColModels::ms_colModelPed1);
+ mi->m_pedType = CPedType::FindPedType(pedType);
+ mi->m_pedStatType = CPedStats::GetPedStatType(pedStats);
+ for(animGroupId = 0; animGroupId < NUM_ANIM_ASSOC_GROUPS; animGroupId++)
+ if(strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)animGroupId)) == 0)
+ break;
+ mi->m_animGroup = animGroupId;
+
+ // ???
+ CModelInfo::GetModelInfo(MI_LOPOLYGUY)->SetColModel(&CTempColModels::ms_colModelPed1);
+}
+
+int
+CFileLoader::LoadPathHeader(const char *line, char *type)
+{
+ int id;
+ char modelname[32];
+
+ sscanf(line, "%s %d %s", type, &id, modelname);
+ return id;
+}
+
+void
+CFileLoader::LoadPedPathNode(const char *line, int id, int node)
+{
+ int type, next, cross;
+ float x, y, z, width;
+
+ sscanf(line, "%d %d %d %f %f %f %f", &type, &next, &cross, &x, &y, &z, &width);
+ ThePaths.StoreNodeInfoPed(id, node, type, next, x, y, z, 0, !!cross);
+}
+
+void
+CFileLoader::LoadCarPathNode(const char *line, int id, int node)
+{
+ int type, next, cross, numLeft, numRight;
+ float x, y, z, width;
+
+ sscanf(line, "%d %d %d %f %f %f %f %d %d", &type, &next, &cross, &x, &y, &z, &width, &numLeft, &numRight);
+ ThePaths.StoreNodeInfoCar(id, node, type, next, x, y, z, 0, numLeft, numRight);
+}
+
+
+void
+CFileLoader::Load2dEffect(const char *line)
+{
+ int id, r, g, b, a, type;
+ float x, y, z;
+ char corona[32], shadow[32];
+ int shadowIntens, lightType, roadReflection, flare, flags, probability;
+ CBaseModelInfo *mi;
+ C2dEffect *effect;
+ char *p;
+
+ sscanf(line, "%d %f %f %f %d %d %d %d %d", &id, &x, &y, &z, &r, &g, &b, &a, &type);
+
+ CTxdStore::PushCurrentTxd();
+ CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle"));
+
+ mi = CModelInfo::GetModelInfo(id);
+ effect = CModelInfo::Get2dEffectStore().alloc();
+ mi->Add2dEffect(effect);
+ effect->pos = CVector(x, y, z);
+ effect->col = CRGBA(r, g, b, a);
+ effect->type = type;
+
+ switch(effect->type){
+ case EFFECT_LIGHT:
+ while(*line++ != '"');
+ p = corona;
+ while(*line != '"') *p++ = *line++;
+ *p = '\0';
+ line++;
+
+ while(*line++ != '"');
+ p = shadow;
+ while(*line != '"') *p++ = *line++;
+ *p = '\0';
+ line++;
+
+ sscanf(line, "%f %f %f %f %d %d %d %d %d",
+ &effect->light.dist,
+ &effect->light.range,
+ &effect->light.size,
+ &effect->light.shadowRange,
+ &shadowIntens, &lightType, &roadReflection, &flare, &flags);
+ effect->light.corona = RwTextureRead(corona, nil);
+ effect->light.shadow = RwTextureRead(shadow, nil);
+ effect->light.shadowIntensity = shadowIntens;
+ effect->light.lightType = lightType;
+ effect->light.roadReflection = roadReflection;
+ effect->light.flareType = flare;
+ // TODO: check out the flags
+ if(flags & 4)
+ flags &= ~2;
+ effect->light.flags = flags;
+ break;
+
+ case EFFECT_PARTICLE:
+ sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %f",
+ &id, &x, &y, &z, &r, &g, &b, &a, &type,
+ &effect->particle.particleType,
+ &effect->particle.dir.x,
+ &effect->particle.dir.y,
+ &effect->particle.dir.z,
+ &effect->particle.scale);
+ break;
+
+ case EFFECT_ATTRACTOR:
+ sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %d",
+ &id, &x, &y, &z, &r, &g, &b, &a, &type,
+ &flags,
+ &effect->attractor.dir.x,
+ &effect->attractor.dir.y,
+ &effect->attractor.dir.z,
+ &probability);
+ effect->attractor.flags = flags;
+ effect->attractor.probability = probability;
+ break;
+ }
+
+ CTxdStore::PopCurrentTxd();
+}
+
+void
+CFileLoader::LoadScene(const char *filename)
+{
+ enum {
+ NONE,
+ INST,
+ ZONE,
+ CULL,
+ PICK,
+ PATH,
+ };
+ char *line;
+ int fd;
+ int section;
+ int pathIndex;
+ char pathTypeStr[20];
+
+ section = NONE;
+ pathIndex = -1;
+ debug("Creating objects from %s...\n", filename);
+
+ fd = CFileMgr::OpenFile(filename, "rb");
+ for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){
+ if(*line == '\0' || *line == '#')
+ continue;
+
+ if(section == NONE){
+ if(strncmp(line, "inst", 4) == 0) section = INST;
+ else if(strncmp(line, "zone", 4) == 0) section = ZONE;
+ else if(strncmp(line, "cull", 4) == 0) section = CULL;
+ else if(strncmp(line, "pick", 4) == 0) section = PICK;
+ else if(strncmp(line, "path", 4) == 0) section = PATH;
+ }else if(strncmp(line, "end", 3) == 0){
+ section = NONE;
+ }else switch(section){
+ case INST:
+ LoadObjectInstance(line);
+ break;
+ case ZONE:
+ LoadZone(line);
+ break;
+ case CULL:
+ LoadCullZone(line);
+ break;
+ case PICK:
+ // unused
+ LoadPickup(line);
+ break;
+ case PATH:
+ // unfinished in the game
+ if(pathIndex == -1){
+ LoadPathHeader(line, pathTypeStr);
+ // type not set
+ pathIndex = 0;
+ }else{
+ // nodes not loaded
+ pathIndex++;
+ if(pathIndex == 12)
+ pathIndex = -1;
+ }
+ break;
+ }
+ }
+ CFileMgr::CloseFile(fd);
+
+ debug("Finished loading IPL\n");
+}
+
+void
+CFileLoader::LoadObjectInstance(const char *line)
+{
+ int id;
+ char name[24];
+ RwV3d trans, scale, axis;
+ float angle;
+ CSimpleModelInfo *mi;
+ RwMatrix *xform;
+ CEntity *entity;
+
+ if(sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f",
+ &id, name,
+ &trans.x, &trans.y, &trans.z,
+ &scale.x, &scale.y, &scale.z,
+ &axis.x, &axis.y, &axis.z, &angle) != 12)
+ return;
+
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id);
+ if(mi == nil)
+ return;
+ assert(mi->IsSimple());
+
+ angle = -RADTODEG(2.0f * acosf(angle));
+ xform = RwMatrixCreate();
+ RwMatrixRotate(xform, &axis, angle, rwCOMBINEREPLACE);
+ RwMatrixTranslate(xform, &trans, rwCOMBINEPOSTCONCAT);
+
+ if(mi->GetObjectID() == -1){
+ if(ThePaths.IsPathObject(id)){
+ entity = new CTreadable;
+ ThePaths.RegisterMapObject((CTreadable*)entity);
+ }else
+ entity = new CBuilding;
+ entity->SetModelIndexNoCreate(id);
+ entity->GetMatrix() = CMatrix(xform);
+ entity->m_level = CTheZones::GetLevelFromPosition(entity->GetPosition());
+ if(mi->IsSimple()){
+ if(mi->m_isBigBuilding)
+ entity->SetupBigBuilding();
+ if(mi->m_isSubway)
+ entity->bIsSubway = true;
+ }
+ if(mi->GetLargestLodDistance() < 2.0f)
+ entity->bIsVisible = false;
+ CWorld::Add(entity);
+ }else{
+ entity = new CDummyObject;
+ entity->SetModelIndexNoCreate(id);
+ entity->GetMatrix() = CMatrix(xform);
+ CWorld::Add(entity);
+ if(IsGlass(entity->GetModelIndex()))
+ entity->bIsVisible = false;
+ entity->m_level = CTheZones::GetLevelFromPosition(entity->GetPosition());
+ }
+
+ RwMatrixDestroy(xform);
+}
+
+void
+CFileLoader::LoadZone(const char *line)
+{
+ char name[24];
+ int type, level;
+ float minx, miny, minz;
+ float maxx, maxy, maxz;
+
+ if(sscanf(line, "%s %d %f %f %f %f %f %f %d", name, &type, &minx, &miny, &minz, &maxx, &maxy, &maxz, &level) == 9)
+ CTheZones::CreateZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level);
+}
+
+void
+CFileLoader::LoadCullZone(const char *line)
+{
+ CVector pos;
+ float minx, miny, minz;
+ float maxx, maxy, maxz;
+ int flags;
+ int wantedLevelDrop = 0;
+
+ sscanf(line, "%f %f %f %f %f %f %f %f %f %d %d",
+ &pos.x, &pos.y, &pos.z,
+ &minx, &miny, &minz,
+ &maxx, &maxy, &maxz,
+ &flags, &wantedLevelDrop);
+ CCullZones::AddCullZone(pos, minx, maxx, miny, maxy, minz, maxz, flags, wantedLevelDrop);
+}
+
+// unused
+void
+CFileLoader::LoadPickup(const char *line)
+{
+ int id;
+ float x, y, z;
+
+ sscanf(line, "%d %f %f %f", &id, &x, &y, &z);
+}
+
+void
+CFileLoader::LoadMapZones(const char *filename)
+{
+ enum {
+ NONE,
+ INST,
+ ZONE,
+ CULL,
+ PICK,
+ PATH,
+ };
+ char *line;
+ int fd;
+ int section;
+
+ section = NONE;
+ debug("Creating zones from %s...\n", filename);
+
+ fd = CFileMgr::OpenFile(filename, "rb");
+ for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){
+ if(*line == '\0' || *line == '#')
+ continue;
+
+ if(section == NONE){
+ if(strncmp(line, "zone", 4) == 0) section = ZONE;
+ }else if(strncmp(line, "end", 3) == 0){
+ section = NONE;
+ }else switch(section){
+ case ZONE: {
+ char name[24];
+ int type, level;
+ float minx, miny, minz;
+ float maxx, maxy, maxz;
+ if(sscanf(line, "%s %d %f %f %f %f %f %f %d",
+ &name, &type,
+ &minx, &miny, &minz,
+ &maxx, &maxy, &maxz,
+ &level) == 9)
+ CTheZones::CreateMapZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level);
+ }
+ break;
+ }
+ }
+ CFileMgr::CloseFile(fd);
+
+ debug("Finished loading IPL\n");
+}
+
+
+STARTPATCHES
+ InjectHook(0x476290, CFileLoader::LoadLevel, PATCH_JUMP);
+
+ InjectHook(0x476520, CFileLoader::LoadCollisionFromDatFile, PATCH_JUMP);
+ InjectHook(0x4761D0, CFileLoader::LoadLine, PATCH_JUMP);
+ InjectHook(0x4765B0, CFileLoader::LoadTexDictionary, PATCH_JUMP);
+ InjectHook(0x478B20, CFileLoader::LoadCollisionFile, PATCH_JUMP);
+ InjectHook(0x478C20, CFileLoader::LoadCollisionModel, PATCH_JUMP);
+ InjectHook(0x476750, CFileLoader::LoadModelFile, PATCH_JUMP);
+ InjectHook(0x476810, (void (*)(const char*))CFileLoader::LoadClumpFile, PATCH_JUMP);
+ InjectHook(0x476990, (bool (*)(RwStream*,uint32))CFileLoader::LoadClumpFile, PATCH_JUMP);
+ InjectHook(0x476A20, CFileLoader::StartLoadClumpFile, PATCH_JUMP);
+ InjectHook(0x476A70, CFileLoader::FinishLoadClumpFile, PATCH_JUMP);
+ InjectHook(0x476930, CFileLoader::LoadAtomicFile, PATCH_JUMP);
+ InjectHook(0x4767C0, CFileLoader::LoadAtomicFile2Return, PATCH_JUMP);
+ InjectHook(0x476630, CFileLoader::AddTexDictionaries, PATCH_JUMP);
+
+ InjectHook(0x476AC0, CFileLoader::LoadObjectTypes, PATCH_JUMP);
+ InjectHook(0x477040, CFileLoader::LoadObject, PATCH_JUMP);
+ InjectHook(0x4774B0, CFileLoader::LoadTimeObject, PATCH_JUMP);
+ InjectHook(0x477920, CFileLoader::LoadClumpObject, PATCH_JUMP);
+ InjectHook(0x477990, CFileLoader::LoadVehicleObject, PATCH_JUMP);
+ InjectHook(0x477DE0, CFileLoader::LoadPedObject, PATCH_JUMP);
+ InjectHook(0x477ED0, CFileLoader::LoadPathHeader, PATCH_JUMP);
+ InjectHook(0x477FF0, CFileLoader::LoadCarPathNode, PATCH_JUMP);
+ InjectHook(0x477F00, CFileLoader::LoadPedPathNode, PATCH_JUMP);
+ InjectHook(0x4780E0, CFileLoader::Load2dEffect, PATCH_JUMP);
+
+ InjectHook(0x478370, CFileLoader::LoadScene, PATCH_JUMP);
+ InjectHook(0x4786B0, CFileLoader::LoadObjectInstance, PATCH_JUMP);
+ InjectHook(0x478A00, CFileLoader::LoadZone, PATCH_JUMP);
+ InjectHook(0x478A90, CFileLoader::LoadCullZone, PATCH_JUMP);
+
+ InjectHook(0x478550, CFileLoader::LoadMapZones, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/FileLoader.h b/src/core/FileLoader.h
new file mode 100644
index 00000000..f9121ace
--- /dev/null
+++ b/src/core/FileLoader.h
@@ -0,0 +1,42 @@
+#pragma once
+
+class CFileLoader
+{
+ static char ms_line[256];
+public:
+ static void LoadLevel(const char *filename);
+ static void LoadCollisionFromDatFile(int currlevel);
+ static char *LoadLine(int fd);
+ static RwTexDictionary *LoadTexDictionary(const char *filename);
+ static void LoadCollisionFile(const char *filename);
+ static void LoadCollisionModel(uint8 *buf, CColModel &model, char *name);
+ static void LoadModelFile(const char *filename);
+ static RpAtomic *FindRelatedModelInfoCB(RpAtomic *atomic, void *data);
+ static void LoadClumpFile(const char *filename);
+ static bool LoadClumpFile(RwStream *stream, uint32 id);
+ static bool StartLoadClumpFile(RwStream *stream, uint32 id);
+ static bool FinishLoadClumpFile(RwStream *stream, uint32 id);
+ static bool LoadAtomicFile(RwStream *stream, uint32 id);
+ static RpAtomic *SetRelatedModelInfoCB(RpAtomic *atomic, void *data);
+ static RpClump *LoadAtomicFile2Return(const char *filename);
+ static void AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src);
+
+ static void LoadObjectTypes(const char *filename);
+ static void LoadObject(const char *line);
+ static void LoadTimeObject(const char *line);
+ static void LoadClumpObject(const char *line);
+ static void LoadVehicleObject(const char *line);
+ static void LoadPedObject(const char *line);
+ static int LoadPathHeader(const char *line, char *type);
+ static void LoadPedPathNode(const char *line, int id, int node);
+ static void LoadCarPathNode(const char *line, int id, int node);
+ static void Load2dEffect(const char *line);
+
+ static void LoadScene(const char *filename);
+ static void LoadObjectInstance(const char *line);
+ static void LoadZone(const char *line);
+ static void LoadCullZone(const char *line);
+ static void LoadPickup(const char *line);
+
+ static void LoadMapZones(const char *filename);
+};
diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp
new file mode 100644
index 00000000..954fcdef
--- /dev/null
+++ b/src/core/FileMgr.cpp
@@ -0,0 +1,300 @@
+#define _CRT_SECURE_NO_WARNINGS
+#include <fcntl.h>
+#include <direct.h>
+#include "common.h"
+#include "patcher.h"
+#include "FileMgr.h"
+
+const char *_psGetUserFilesFolder();
+
+/*
+ * Windows FILE is BROKEN for GTA.
+ *
+ * We need to support mapping between LF and CRLF for text files
+ * but we do NOT want to end the file at the first sight of a SUB character.
+ * So here is a simple implementation of a FILE interface that works like GTA expects.
+ */
+
+struct myFILE
+{
+ bool isText;
+ FILE *file;
+};
+
+#define NUMFILES 20
+static myFILE myfiles[NUMFILES];
+
+/* Force file to open as binary but remember if it was text mode */
+static int
+myfopen(const char *filename, const char *mode)
+{
+ int fd;
+ char realmode[10], *p;
+
+ for(fd = 1; fd < NUMFILES; fd++)
+ if(myfiles[fd].file == nil)
+ goto found;
+ return 0; // no free fd
+found:
+ myfiles[fd].isText = strchr(mode, 'b') == nil;
+ p = realmode;
+ while(*mode)
+ if(*mode != 't' && *mode != 'b')
+ *p++ = *mode++;
+ else
+ mode++;
+ *p++ = 'b';
+ *p = '\0';
+ myfiles[fd].file = fopen(filename, realmode);
+ if(myfiles[fd].file == nil)
+ return 0;
+ return fd;
+}
+
+static int
+myfclose(int fd)
+{
+ int ret;
+ assert(fd < NUMFILES);
+ if(myfiles[fd].file){
+ ret = fclose(myfiles[fd].file);
+ myfiles[fd].file = nil;
+ return ret;
+ }
+ return EOF;
+}
+
+static int
+myfgetc(int fd)
+{
+ int c;
+ c = fgetc(myfiles[fd].file);
+ if(myfiles[fd].isText && c == 015){
+ /* translate CRLF to LF */
+ c = fgetc(myfiles[fd].file);
+ if(c == 012)
+ return c;
+ ungetc(c, myfiles[fd].file);
+ return 015;
+ }
+ return c;
+}
+
+static int
+myfputc(int c, int fd)
+{
+ /* translate LF to CRLF */
+ if(myfiles[fd].isText && c == 012)
+ fputc(015, myfiles[fd].file);
+ return fputc(c, myfiles[fd].file);
+}
+
+static char*
+myfgets(char *buf, int len, int fd)
+{
+ int c;
+ char *p;
+
+ p = buf;
+ len--; // NUL byte
+ while(len--){
+ c = myfgetc(fd);
+ if(c == EOF){
+ if(p == buf)
+ return nil;
+ break;
+ }
+ *p++ = c;
+ if(c == '\n')
+ break;
+ }
+ *p = '\0';
+ return buf;
+}
+
+static int
+myfread(void *buf, size_t elt, size_t n, int fd)
+{
+ if(myfiles[fd].isText){
+ char *p;
+ size_t i;
+ int c;
+
+ n *= elt;
+ p = (char*)buf;
+ for(i = 0; i < n; i++){
+ c = myfgetc(fd);
+ if(c == EOF)
+ break;
+ *p++ = c;
+ }
+ return i / elt;
+ }
+ return fread(buf, elt, n, myfiles[fd].file);
+}
+
+static int
+myfwrite(void *buf, size_t elt, size_t n, int fd)
+{
+ if(myfiles[fd].isText){
+ char *p;
+ size_t i;
+ int c;
+
+ n *= elt;
+ p = (char*)buf;
+ for(i = 0; i < n; i++){
+ c = *p++;
+ myfputc(c, fd);
+ if(feof(myfiles[fd].file)) // is this right?
+ break;
+ }
+ return i / elt;
+ }
+ return fwrite(buf, elt, n, myfiles[fd].file);
+}
+
+static int
+myfseek(int fd, long offset, int whence)
+{
+ return fseek(myfiles[fd].file, offset, whence);
+}
+
+static int
+myfeof(int fd)
+{
+ return feof(myfiles[fd].file);
+// return ferror(myfiles[fd].file);
+}
+
+
+char *CFileMgr::ms_rootDirName = (char*)0x5F18F8;
+char *CFileMgr::ms_dirName = (char*)0x713CA8;
+
+void
+CFileMgr::Initialise(void)
+{
+ _getcwd(ms_rootDirName, 128);
+ strcat(ms_rootDirName, "\\");
+}
+
+void
+CFileMgr::ChangeDir(const char *dir)
+{
+ if(*dir == '\\'){
+ strcpy(ms_dirName, ms_rootDirName);
+ dir++;
+ }
+ if(*dir != '\0'){
+ strcat(ms_dirName, dir);
+ // BUG in the game it seems, it's off by one
+ if(dir[strlen(dir)-1] != '\\')
+ strcat(ms_dirName, "\\");
+ }
+ chdir(ms_dirName);
+}
+
+void
+CFileMgr::SetDir(const char *dir)
+{
+ strcpy(ms_dirName, ms_rootDirName);
+ if(*dir != '\0'){
+ strcat(ms_dirName, dir);
+ // BUG in the game it seems, it's off by one
+ if(dir[strlen(dir)-1] != '\\')
+ strcat(ms_dirName, "\\");
+ }
+ chdir(ms_dirName);
+}
+
+void
+CFileMgr::SetDirMyDocuments(void)
+{
+ SetDir(""); // better start at the root if user directory is relative
+ chdir(_psGetUserFilesFolder());
+}
+
+int
+CFileMgr::LoadFile(const char *file, uint8 *buf, int unused, const char *mode)
+{
+ int fd;
+ int n, len;
+
+ fd = myfopen(file, mode);
+ if(fd == 0)
+ return 0;
+ len = 0;
+ do{
+ n = myfread(buf + len, 1, 0x4000, fd);
+ if(n < 0)
+ return -1;
+ len += n;
+ }while(n == 0x4000);
+ buf[len] = 0;
+ myfclose(fd);
+ return len;
+}
+
+int
+CFileMgr::OpenFile(const char *file, const char *mode)
+{
+ return myfopen(file, mode);
+}
+
+int
+CFileMgr::OpenFileForWriting(const char *file)
+{
+ return OpenFile(file, "wb");
+}
+
+int
+CFileMgr::Read(int fd, char *buf, int len)
+{
+ return myfread(buf, 1, len, fd);
+}
+
+int
+CFileMgr::Write(int fd, char *buf, int len)
+{
+ return myfwrite(buf, 1, len, fd);
+}
+
+bool
+CFileMgr::Seek(int fd, int offset, int whence)
+{
+ return !!myfseek(fd, offset, whence);
+}
+
+bool
+CFileMgr::ReadLine(int fd, char *buf, int len)
+{
+ return myfgets(buf, len, fd) != nil;
+}
+
+int
+CFileMgr::CloseFile(int fd)
+{
+ return myfclose(fd);
+}
+
+int
+CFileMgr::GetErrorReadWrite(int fd)
+{
+ return myfeof(fd);
+}
+
+STARTPATCHES
+ InjectHook(0x478F80, CFileMgr::Initialise, PATCH_JUMP);
+ InjectHook(0x478FB0, CFileMgr::ChangeDir, PATCH_JUMP);
+ InjectHook(0x479020, CFileMgr::SetDir, PATCH_JUMP);
+ InjectHook(0x479080, CFileMgr::SetDirMyDocuments, PATCH_JUMP);
+ InjectHook(0x479090, CFileMgr::LoadFile, PATCH_JUMP);
+ InjectHook(0x479100, CFileMgr::OpenFile, PATCH_JUMP);
+ InjectHook(0x479120, CFileMgr::OpenFileForWriting, PATCH_JUMP);
+ InjectHook(0x479140, CFileMgr::Read, PATCH_JUMP);
+ InjectHook(0x479160, CFileMgr::Write, PATCH_JUMP);
+ InjectHook(0x479180, CFileMgr::Seek, PATCH_JUMP);
+ InjectHook(0x4791D0, CFileMgr::ReadLine, PATCH_JUMP);
+ InjectHook(0x479200, CFileMgr::CloseFile, PATCH_JUMP);
+ InjectHook(0x479210, CFileMgr::GetErrorReadWrite, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/FileMgr.h b/src/core/FileMgr.h
new file mode 100644
index 00000000..bab86e38
--- /dev/null
+++ b/src/core/FileMgr.h
@@ -0,0 +1,21 @@
+#pragma once
+
+class CFileMgr
+{
+ static char *ms_rootDirName; //[128];
+ static char *ms_dirName; //[128];
+public:
+ static void Initialise(void);
+ static void ChangeDir(const char *dir);
+ static void SetDir(const char *dir);
+ static void SetDirMyDocuments(void);
+ static int LoadFile(const char *file, uint8 *buf, int unused, const char *mode);
+ static int OpenFile(const char *file, const char *mode);
+ static int OpenFileForWriting(const char *file);
+ static int Read(int fd, char *buf, int len);
+ static int Write(int fd, char *buf, int len);
+ static bool Seek(int fd, int offset, int whence);
+ static bool ReadLine(int fd, char *buf, int len);
+ static int CloseFile(int fd);
+ static int GetErrorReadWrite(int fd);
+};
diff --git a/src/core/Fire.cpp b/src/core/Fire.cpp
new file mode 100644
index 00000000..05d72199
--- /dev/null
+++ b/src/core/Fire.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "Fire.h"
+
+WRAPPER void CFire::Extinguish(void) { EAXJMP(0x479D40); } \ No newline at end of file
diff --git a/src/core/Fire.h b/src/core/Fire.h
new file mode 100644
index 00000000..c7f83fd8
--- /dev/null
+++ b/src/core/Fire.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "common.h"
+#include "Entity.h"
+
+class CFire
+{
+ char m_bIsOngoing;
+ char m_bExists;
+ char m_bPropogationFlag;
+ char m_bAudioSet;
+ CVector m_vecPos;
+ CEntity *m_pEntity;
+ CEntity *m_pSource;
+ int m_nExtinguishTime;
+ int m_nStartTime;
+ int field_20;
+ int field_24;
+ int field_28;
+ float field_2C;
+
+public:
+ void Extinguish(void);
+}; \ No newline at end of file
diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp
new file mode 100644
index 00000000..fdb2420b
--- /dev/null
+++ b/src/core/Frontend.cpp
@@ -0,0 +1,2353 @@
+#define DIRECTINPUT_VERSION 0x0800
+#include <dinput.h>
+#include "common.h"
+#include "patcher.h"
+#include "win.h"
+#include "Frontend.h"
+#include "Font.h"
+#include "Pad.h"
+#include "Text.h"
+#include "main.h"
+#include "Timer.h"
+#include "Game.h"
+#include "DMAudio.h"
+#include "MusicManager.h"
+#include "FileMgr.h"
+#include "Streaming.h"
+#include "TxdStore.h"
+#include "General.h"
+#include "PCSave.h"
+#include "Script.h"
+#include "Camera.h"
+#include "MenuScreens.h"
+#include "ControllerConfig.h"
+#include "Vehicle.h"
+#include "MBlur.h"
+#include "PlayerSkin.h"
+
+int32 &CMenuManager::OS_Language = *(int32*)0x5F2F78;
+int8 &CMenuManager::m_PrefsUseVibration = *(int8*)0x95CD92;
+int8 &CMenuManager::m_DisplayControllerOnFoot = *(int8*)0x95CD8D;
+int8 &CMenuManager::m_PrefsVsync = *(int8*)0x5F2E58;
+int8 &CMenuManager::m_PrefsVsyncDisp = *(int8*)0x5F2E5C;
+int8 &CMenuManager::m_PrefsFrameLimiter = *(int8*)0x5F2E60;
+int8 &CMenuManager::m_PrefsShowSubtitles = *(int8*)0x5F2E54;
+int8 &CMenuManager::m_PrefsSpeakers = *(int8*)0x95CD7E;
+int8 &CMenuManager::m_ControlMethod = *(int8*)0x8F5F7C;
+int8 &CMenuManager::m_PrefsDMA = *(int8*)0x5F2F74;
+int8 &CMenuManager::m_PrefsLanguage = *(int8*)0x941238;
+
+bool &CMenuManager::m_PrefsAllowNastyGame = *(bool*)0x5F2E64;
+bool &CMenuManager::m_bStartUpFrontEndRequested = *(bool*)0x95CCF4;
+bool &CMenuManager::m_bShutDownFrontEndRequested = *(bool*)0x95CD6A;
+
+int8 &CMenuManager::m_PrefsUseWideScreen = *(int8*)0x95CD23;
+int8 &CMenuManager::m_PrefsRadioStation = *(int8*)0x95CDA4;
+int8 &CMenuManager::m_bDisableMouseSteering = *(int8*)0x60252C;
+int32 &CMenuManager::m_PrefsBrightness = *(int32*)0x5F2E50;
+float &CMenuManager::m_PrefsLOD = *(float*)0x8F42C4;
+int8 &CMenuManager::m_bFrontEnd_ReloadObrTxtGxt = *(int8*)0x628CFC;
+int32 &CMenuManager::m_PrefsMusicVolume = *(int32*)0x5F2E4C;
+int32 &CMenuManager::m_PrefsSfxVolume = *(int32*)0x5F2E48;
+
+uint8 *CMenuManager::m_PrefsSkinFile = (uint8*)0x5F2E74;
+
+CMenuManager &FrontEndMenuManager = *(CMenuManager*)0x8F59D8;
+
+// Move this somewhere else.
+float lodMultiplier = *(float*)0x5F726C;
+
+// Stuff not in CMenuManager:
+int VibrationTime;
+char* pEditString;
+int32 pControlEdit;
+int8 DisplayComboButtonErrMsg;
+bool MouseButtonJustClicked;
+bool JoyButtonJustClicked;
+
+// Frontend inputs.
+bool GetPadBack();
+bool GetPadExitEnter();
+bool GetPadForward();
+bool GetPadMoveUp();
+bool GetPadMoveDown();
+bool GetPadMoveLeft();
+bool GetPadMoveRight();
+bool GetMouseForward();
+bool GetMouseBack();
+bool GetMousePos();
+bool GetMouseMoveLeft();
+bool GetMouseMoveRight();
+bool GetPadInput();
+bool GetMouseInput();
+
+char *FrontendFilenames[] = {
+ "fe2_mainpanel_ul",
+ "fe2_mainpanel_ur",
+ "fe2_mainpanel_dl",
+ "fe2_mainpanel_dr",
+ "fe2_mainpanel_dr2",
+ "fe2_tabactive",
+ "fe_iconbrief",
+ "fe_iconstats",
+ "fe_iconcontrols",
+ "fe_iconsave",
+ "fe_iconaudio",
+ "fe_icondisplay",
+ "fe_iconlanguage",
+ "fe_controller",
+ "fe_controllersh",
+ "fe_arrows1",
+ "fe_arrows2",
+ "fe_arrows3",
+ "fe_arrows4",
+ "fe_radio1", // HEAD_RADIO
+ "fe_radio2", // DOUBLE_CLEF
+ "fe_radio5", // JAH_RADIO
+ "fe_radio7", // RISE_FM
+ "fe_radio8", // LIPS_106
+ "fe_radio3", // GAME_FM
+ "fe_radio4", // MSX_FM
+ "fe_radio6", // FLASHBACK
+ "fe_radio9", // CHATTERBOX
+};
+
+char *MenuFilenames[] = {
+ "connection24", "",
+ "findgame24", "",
+ "hostgame24", "",
+ "mainmenu24", "",
+ "Playersetup24", "",
+ "singleplayer24", "",
+ "multiplayer24", "",
+ "dmalogo128", "dmalogo128m",
+ "gtaLogo128", "gtaLogo128",
+ "rockstarLogo128", "rockstarlogo128m",
+ "gamespy256", "gamespy256a",
+ "mouse", "mousetimera",
+ "mousetimer", "mousetimera",
+ "mp3logo", "mp3logoA",
+ "downOFF", "buttonA",
+ "downON", "buttonA",
+ "upOFF", "buttonA",
+ "upON", "buttonA",
+ "gta3logo256", "gta3logo256m",
+ nil, nil
+};
+
+#if 1
+WRAPPER void CMenuManager::BuildStatLine(char *, void *, uint16, void *) { EAXJMP(0x483870); }
+#else
+void CMenuManager::BuildStatLine(char *, void *, uint16, void *)
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::CentreMousePointer() { EAXJMP(0x48ACE0); }
+#else
+void CMenuManager::CentreMousePointer()
+{
+ tagPOINT Point;
+
+ if (SCREEN_WIDTH * 0.5f == 0.0f && 0.0f == SCREEN_HEIGHT * 0.5f) {
+ Point.x = SCREEN_WIDTH / 2;
+ Point.y = SCREEN_HEIGHT / 2;
+ ClientToScreen(PSGLOBAL(window), &Point);
+ SetCursorPos(Point.x, Point.y);
+
+ PSGLOBAL(lastMousePos.x) = SCREEN_WIDTH / 2;
+ PSGLOBAL(lastMousePos.y) = SCREEN_HEIGHT / 2;
+ }
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::CheckCodesForControls(int, int) { EAXJMP(0x48A950); }
+#else
+void CMenuManager::CheckCodesForControls()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER bool CMenuManager::CheckHover(int, int, int, int) { EAXJMP(0x48ACA0); }
+#else
+bool CMenuManager::CheckHover(int x1, int x2, int y1, int y2)
+{
+ return m_nMousePosX > x1 && m_nMousePosX < x2 &&
+ m_nMousePosY > y1 && m_nMousePosY < y2;
+}
+#endif
+
+void CMenuManager::CheckSliderMovement(int value)
+{
+ float fBrightness = 0.0f;
+ float fDrawDistance = 0.0f;
+ float fRadioVolume = 0.0f;
+ float fSfxVolume = 0.0f;
+ float fMouseSens = 0.0f;
+
+ switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) {
+ case MENUACTION_BRIGHTNESS:
+ fBrightness = m_PrefsBrightness + (value * (512.0f) / 16.0f);
+
+ if (fBrightness > 511.0f)
+ fBrightness = 511.0f;
+ else if (fBrightness < 0.0f)
+ fBrightness = 0.0f;
+
+ m_PrefsBrightness = fBrightness;
+ SaveSettings();
+ break;
+ case MENUACTION_DRAWDIST:
+ fDrawDistance = m_PrefsLOD + (value * (1.8f - 0.8f) / 16.0f);
+
+ if (fDrawDistance > 1.8f)
+ fDrawDistance = 1.8f;
+ else if (fDrawDistance < 0.8f)
+ fDrawDistance = 0.8f;
+
+ m_PrefsLOD = fDrawDistance;
+ SaveSettings();
+ break;
+ case MENUACTION_MUSICVOLUME:
+ fRadioVolume = m_PrefsMusicVolume + (value * (128.0f) / 16.0f);
+
+ if (fRadioVolume > 127.0f)
+ fRadioVolume = 127.0f;
+ else if (fRadioVolume < 0.0f)
+ fRadioVolume = 0.0f;
+
+ m_PrefsMusicVolume = fRadioVolume;
+ DMAudio.SetMusicMasterVolume(fRadioVolume);
+ SaveSettings();
+ break;
+ case MENUACTION_SFXVOLUME:
+ fSfxVolume = m_PrefsSfxVolume + (value * (128.0f) / 16.0f);
+
+ if (fSfxVolume > 127)
+ fSfxVolume = 127;
+ else if (fSfxVolume < 0.0f)
+ fSfxVolume = 0.0f;
+
+ m_PrefsSfxVolume = fSfxVolume;
+ DMAudio.SetEffectsMasterVolume(fSfxVolume);
+ SaveSettings();
+ break;
+ case MENUACTION_MOUSESENS:
+ fMouseSens = TheCamera.m_fMouseAccelHorzntl + (value * (0.005f - 0.0003125f) / 16.0f);
+
+ if (fMouseSens > 0.005f)
+ fMouseSens = 0.005f;
+ else if (fMouseSens < 0.0003125f)
+ fMouseSens = 0.0003125f;
+
+ TheCamera.m_fMouseAccelHorzntl = fMouseSens;
+
+ // BUG: game doesn't set Y Axis.
+ TheCamera.m_fMouseAccelVertical = fMouseSens;
+ SaveSettings();
+ break;
+ };
+}
+
+#if 1
+WRAPPER int CMenuManager::CostructStatLine(int) { EAXJMP(0x482800); }
+#else
+int CMenuManager::CostructStatLine(int)
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::DisplayHelperText() { EAXJMP(0x48B490); }
+#else
+void CMenuManager::DisplayHelperText()
+{
+ static int32 AlphaText = 255;
+ static int32 Time = 0;
+
+ if (m_nHelperTextMsgId && m_nHelperTextMsgId != 1) {
+ if (CTimer::GetTimeInMillisecondsPauseMode() - Time > 10) {
+ Time = CTimer::GetTimeInMillisecondsPauseMode();
+ m_nHelperTextAlpha -= 2;
+
+ if (AlphaText < 1)
+ ResetHelperText();
+
+ AlphaText = m_nHelperTextAlpha > 255 ? 255 : m_nHelperTextAlpha;
+ }
+ }
+
+ wchar *HelperTextToPrint = nil;
+ // TODO: name this cases?
+ switch (m_nHelperTextMsgId) {
+ case 0:
+ HelperTextToPrint = TheText.Get("FET_MIG");
+ break;
+ case 1:
+ HelperTextToPrint = TheText.Get("FET_APP");
+ break;
+ case 2:
+ HelperTextToPrint = TheText.Get("FET_HRD");
+ break;
+ case 3:
+ HelperTextToPrint = TheText.Get("FET_RSO");
+ break;
+ case 4:
+ HelperTextToPrint = TheText.Get("FET_RSC");
+ break;
+ default:
+ break;
+ };
+
+ CFont::SetAlignment(ALIGN_CENTER);
+ CFont::SetScale(SCREEN_SCALE_X(0.4f), SCREEN_SCALE_Y(0.6f));
+ CFont::SetFontStyle(FONT_HEADING);
+ CFont::SetDropColor(CRGBA(0, 0, 0, AlphaText));
+ CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE);
+ CFont::SetColor(CRGBA(255, 255, 255, AlphaText));
+
+ CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_SCALE_FROM_BOTTOM(120.0f), HelperTextToPrint);
+}
+#endif
+
+#if 0
+WRAPPER float CMenuManager::DisplaySlider(float, float, float, float, float, float) { EAXJMP(0x488420); }
+#else
+float CMenuManager::DisplaySlider(float x, float y, float leftSize, float rightSize, float rectSize, float progress)
+{
+ CRGBA color;
+ float sizeRange;
+
+ float input = 0.0f;
+ for (int i = 0; i < 16; i++) {
+ input = i * rectSize/16.0f + x;
+
+ if (i/16.0f + 1/32.0f < progress)
+ color = CRGBA(255, 217, 106, FadeIn(255));
+ else
+ color = CRGBA(185, 120, 0, FadeIn(255));
+
+ sizeRange = max(leftSize, rightSize);
+
+ float _x = i * rectSize/16.0f + x;
+ float _y = y + sizeRange - ((16 - i) * leftSize + i * rightSize)/16.0f;
+ float _w = SCREEN_SCALE_X(10.0f) + i * rectSize/16.0f + x;
+ float _h = y + sizeRange;
+ float _s = SCREEN_SCALE_X(2.0f);
+ CSprite2d::DrawRect(CRect(_x + _s, _y + _s, _w + _s, _h + _s), CRGBA(0, 0, 0, FadeIn(255))); // Shadow
+ CSprite2d::DrawRect(CRect(i * rectSize/16.0f + x, y + sizeRange - ((16 - i) * leftSize + i * rightSize)/16.0f, SCREEN_SCALE_X(10.0f) + i * rectSize/16.0f + x, y + sizeRange), color);
+ };
+ return input;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::DoSettingsBeforeStartingAGame() { EAXJMP(0x48AB40); }
+#else
+void CMenuManager::DoSettingsBeforeStartingAGame()
+{
+ CCamera::m_bUseMouse3rdPerson = m_ControlMethod == 0;
+ if (m_PrefsVsyncDisp != m_PrefsVsync)
+ m_PrefsVsync = m_PrefsVsyncDisp;
+
+ m_bStartGameLoading = true;
+
+ ShutdownJustMenu();
+ UnloadTextures();
+ DMAudio.SetEffectsFadeVol(0);
+ DMAudio.SetMusicFadeVol(0);
+ DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds());
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::Draw() { EAXJMP(0x47AE00); }
+#else
+void CMenuManager::Draw()
+{
+ CFont::SetBackgroundOff();
+ CFont::SetPropOn();
+ CFont::SetCentreOff();
+ CFont::SetJustifyOn();
+ CFont::SetBackGroundOnlyTextOn();
+ CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(40.0f));
+ CFont::SetRightJustifyWrap(0.0f);
+ CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A)));
+
+ switch (m_nCurrScreen) {
+ case MENUPAGE_STATS:
+ PrintStats();
+ break;
+ case MENUPAGE_BRIEFS:
+ PrintBriefs();
+ break;
+ case MENUPAGE_CONTROLLER_DEBUG:
+ DrawControllerScreenExtraText(0, 350, 20);
+ break;
+ }
+
+ // Header.
+ if (aScreens[m_nCurrScreen].m_ScreenName[0]) {
+ CFont::SetDropShadowPosition(0);
+ CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255)));
+ CFont::SetRightJustifyOn();
+ CFont::SetFontStyle(FONT_HEADING);
+ CFont::SetScale(SCREEN_SCALE_X(MENUHEADER_WIDTH), SCREEN_SCALE_Y(MENUHEADER_HEIGHT));
+ CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName));
+ }
+
+ // Action text.
+ wchar *str;
+ if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) {
+ switch (m_nCurrScreen) {
+ case MENUPAGE_LOAD_SLOT_CONFIRM:
+ if (m_bGameNotLoaded)
+ str = TheText.Get("FES_LCG");
+ else
+ str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName);
+ break;
+ case MENUPAGE_SAVE_OVERWRITE_CONFIRM:
+ if (Slots[m_nCurrSaveSlot] == 1)
+ str = TheText.Get("FESZ_QZ");
+ else
+ str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName);
+ break;
+ case MENUPAGE_EXIT:
+ if (m_bGameNotLoaded)
+ str = TheText.Get("FEQ_SRW");
+ else
+ str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName);
+ break;
+ default:
+ str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName);
+ break;
+ };
+
+ CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE);
+ CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A)));
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::SetScale(SCREEN_SCALE_X(MENUACTION_WIDTH), SCREEN_SCALE_Y(MENUACTION_HEIGHT));
+ CFont::SetAlignment(ALIGN_LEFT);
+ CFont::SetColor(CRGBA(235, 170, 50, FadeIn(255)));
+ CFont::PrintString(SCREEN_SCALE_X(MENUACTION_POS_X), SCREEN_SCALE_Y(MENUACTION_POS_Y), str);
+ }
+
+ for (int i = 0; i < MENUROWS; ++i) {
+ if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0]) {
+ wchar *textToPrint[MENUCOLUMNS] = { nil, nil };
+ bool Locked = false;
+
+ if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) {
+ textToPrint[MENUCOLUMN_LEFT] = GetNameOfSavedGame(i - 1);
+ textToPrint[MENUCOLUMN_RIGHT] = GetSavedGameDateAndTime(i - 1);
+
+ if (!textToPrint[MENUCOLUMN_LEFT][0]) {
+ sprintf(gString, "FEM_SL%d", i);
+ textToPrint[MENUCOLUMN_LEFT] = TheText.Get(gString);
+ }
+ }
+ else {
+ textToPrint[MENUCOLUMN_LEFT] = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName);
+
+ if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_SCREENRES) {
+ if (m_bGameNotLoaded)
+ Locked = false;
+ else
+ Locked = true;
+ }
+ }
+
+ switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) {
+ case MENUACTION_CTRLVIBRATION:
+ break;
+ case MENUACTION_CTRLCONFIG:
+ switch (CPad::GetPad(0)->Mode) {
+ case 0:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF1");
+ break;
+ case 1:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF2");
+ break;
+ case 2:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF3");
+ break;
+ case 3:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF4");
+ break;
+ };
+ break;
+ case MENUACTION_CTRLDISPLAY:
+ break;
+ case MENUACTION_FRAMESYNC:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsVsyncDisp ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_FRAMELIMIT:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsFrameLimiter ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_TRAILS:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CMBlur::BlurOn ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_SUBTITLES:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsShowSubtitles ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_WIDESCREEN:
+#ifndef ASPECT_RATIO_SCALE
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsUseWideScreen ? "FEM_ON" : "FEM_OFF");
+#else
+ switch (m_PrefsUseWideScreen) {
+ case AR_AUTO:
+ textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"AUTO";
+ break;
+ case AR_4_3:
+ textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"4:3";
+ break;
+ case AR_16_9:
+ textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"16:9";
+ break;
+ };
+#endif
+ break;
+ case MENUACTION_RADIO:
+ sprintf(gString, "FEA_FM%d", m_PrefsRadioStation);
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(gString);
+ break;
+ case MENUACTION_SETDBGFLAG:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CTheScripts::DbgFlag ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CTheScripts::DbgFlag ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_INVVERT:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(MousePointerStateHelper.bInvertVertically ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_SCREENRES:
+ {
+ char *res = _psGetVideoModeList()[m_nDisplayVideoMode];
+
+ if (!res)
+ res = "";
+
+ AsciiToUnicode(res, gUString);
+ textToPrint[MENUCOLUMN_RIGHT] = gUString;
+ }
+ break;
+ case MENUACTION_AUDIOHW:
+ if (m_nPrefsAudio3DProviderIndex == -1)
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_NAH");
+ else {
+ char *provider = MusicManager.Get3DProviderName(m_nPrefsAudio3DProviderIndex);
+ AsciiToUnicode(provider, gUString);
+ textToPrint[MENUCOLUMN_RIGHT] = gUString;
+ }
+ break;
+ case MENUACTION_SPEAKERCONF:
+ if (m_nPrefsAudio3DProviderIndex == -1)
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_NAH");
+ else {
+ switch (m_PrefsSpeakers) {
+ case 0:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_2SP");
+ break;
+ case 1:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_EAR");
+ break;
+ case 2:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_4SP");
+ break;
+ };
+ }
+ break;
+ case MENUACTION_CTRLMETHOD:
+ switch (m_ControlMethod) {
+ case 0:
+ textToPrint[MENUCOLUMN_LEFT] = TheText.Get("FET_SCN");
+ break;
+ case 1:
+ textToPrint[MENUCOLUMN_LEFT] = TheText.Get("FET_CCN");
+ break;
+ };
+ break;
+ case MENUACTION_DYNAMICACOUSTIC:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsDMA ? "FEM_ON" : "FEM_OFF");
+ break;
+ case MENUACTION_MOUSESTEER:
+ textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_bDisableMouseSteering ? "FEM_ON" : "FEM_OFF");
+ break;
+ };
+
+ CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE);
+ CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A)));
+ CFont::SetCentreSize(SCREEN_WIDTH);
+ CFont::SetWrapx(SCREEN_WIDTH);
+ CFont::SetRightJustifyWrap(-SCREEN_WIDTH);
+
+ // Set alignment.
+ CVector2D vecPositions = { 0.0f, 0.0f };
+ float fVerticalSpacing;
+ float fBarSize;
+
+ int SavePageSlot =
+ m_nCurrScreen == MENUPAGE_CHOOSE_LOAD_SLOT ||
+ m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT ||
+ m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT;
+
+ if (SavePageSlot) {
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::SetAlignment(ALIGN_LEFT);
+ CFont::SetScale(SCREEN_SCALE_X(0.45f), SCREEN_SCALE_Y(0.7f));
+ fVerticalSpacing = MENUCOLUMN_SPACING_MIN;
+ fBarSize = MENUSELECT_BOX_MIN;
+
+ vecPositions.x = SCREEN_SCALE_X(MENUCOLUMN_SAVE_X);
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_SAVE_Y);
+ }
+ else {
+ CFont::SetFontStyle(FONT_HEADING);
+
+ int LeftMenuColumn =
+ m_nCurrScreen == MENUPAGE_SOUND_SETTINGS ||
+ m_nCurrScreen == MENUPAGE_GRAPHICS_SETTINGS ||
+ m_nCurrScreen == MENUPAGE_MOUSE_CONTROLS;
+
+ if (LeftMenuColumn) {
+ CFont::SetAlignment(ALIGN_LEFT);
+ CFont::SetScale(SCREEN_SCALE_X(0.55f), SCREEN_SCALE_Y(0.8f));
+ fVerticalSpacing = MENUCOLUMN_SPACING_MIN;
+ fBarSize = MENUSELECT_BOX_MIN;
+ }
+ else {
+ CFont::SetAlignment(ALIGN_CENTER);
+ CFont::SetScale(SCREEN_SCALE_X(0.75f), SCREEN_SCALE_Y(0.9f));
+ fVerticalSpacing = MENUCOLUMN_SPACING_MAX;
+ fBarSize = MENUSELECT_BOX_MAX;
+ }
+
+ // Set positions.
+ if (CFont::GetDetails().centre)
+ vecPositions.x = SCREEN_WIDTH / 2;
+ else
+ vecPositions.x = SCREEN_SCALE_X(MENUCOLUMN_POS_X);
+
+ switch (m_nCurrScreen) {
+ case MENUPAGE_BRIEFS:
+ case MENUPAGE_STATS:
+ vecPositions.y = SCREEN_SCALE_FROM_BOTTOM(MENUCOLUMN_FEDS);
+ break;
+ case MENUPAGE_SOUND_SETTINGS:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MAX_Y);
+
+ if (i > 5)
+ vecPositions.y += SCREEN_SCALE_Y(MENURADIO_ICON_H * 1.16f);
+ break;
+ case MENUPAGE_LANGUAGE_SETTINGS:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MIN_Y);
+ break;
+ case MENUPAGE_MOUSE_CONTROLS:
+ case MENUPAGE_GRAPHICS_SETTINGS:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MAX_Y);
+ break;
+ case MENUPAGE_OPTIONS:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y);
+ break;
+ case MENUPAGE_PAUSE_MENU:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_PAUSE_Y);
+ break;
+ case MENUPAGE_NEW_GAME:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y);
+ break;
+ case MENUPAGE_START_MENU:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_START_Y);
+ break;
+ default:
+ vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y);
+ break;
+ };
+ }
+
+ if (i > 0)
+ vecPositions.y += SCREEN_SCALE_Y(fVerticalSpacing * i);
+
+ // Set color and draw selection bar.
+ if (i == m_nCurrOption && m_nMenuFadeAlpha >= 255) {
+ CFont::SetColor(CRGBA(255, 217, 106, FadeIn(255)));
+ CSprite2d::DrawRect(CRect(SCREEN_STRETCH_X(11.0f), vecPositions.y - SCREEN_STRETCH_Y(fBarSize * 0.13f), SCREEN_STRETCH_FROM_RIGHT(11.0f), vecPositions.y + SCREEN_STRETCH_Y(fBarSize)), CRGBA(100, 200, 50, 50));
+ }
+ else
+ CFont::SetColor(CRGBA(235, 170, 50, FadeIn(255)));
+
+ // Draw
+ if (textToPrint[MENUCOLUMN_LEFT])
+ CFont::PrintString(vecPositions.x, vecPositions.y, textToPrint[MENUCOLUMN_LEFT]);
+
+ if (textToPrint[MENUCOLUMN_RIGHT]) {
+ if (Locked)
+ CFont::SetColor(CRGBA(190, 130, 40, FadeIn(255)));
+
+ CFont::SetAlignment(ALIGN_RIGHT);
+ CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SavePageSlot ? MENUCOLUMN_SAVE_X : MENUCOLUMN_POS_X), vecPositions.y, textToPrint[MENUCOLUMN_RIGHT]);
+ }
+
+ // Mouse support.
+ // TODO: inputs for these pages.
+ if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) {
+ }
+ else if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) {
+
+ }
+ else {
+ static bool bIsMouseInPosition = false;
+ if (m_nMenuFadeAlpha >= 255 && GetMouseInput()) {
+ CVector2D vecInputSize = { SCREEN_SCALE_X(20.0f), SCREEN_SCALE_FROM_RIGHT(20.0f) };
+ if (m_bShowMouse &&
+ ((CheckHover(vecInputSize.x, vecInputSize.y, vecPositions.y, vecPositions.y + SCREEN_STRETCH_Y(20.0f)))))
+ bIsMouseInPosition = true;
+ else
+ bIsMouseInPosition = false;
+
+ if (bIsMouseInPosition) {
+ if (m_nCurrOption != i) {
+ m_nCurrOption = i;
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0);
+ }
+
+ m_nPrevOption = m_nCurrOption;
+
+ if (GetMouseForward())
+ m_nHoverOption = IGNORE_OPTION;
+ else
+ m_nHoverOption = ACTIVATE_OPTION;
+ }
+ }
+ }
+
+ // Sliders
+ // TODO: CheckHover
+ switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) {
+ case MENUACTION_BRIGHTNESS:
+ DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsBrightness/512.0f);
+ break;
+ case MENUACTION_DRAWDIST:
+ DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), (m_PrefsLOD - 0.8f) * 1.0f);
+ break;
+ case MENUACTION_MUSICVOLUME:
+ DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsMusicVolume/128.0f);
+ break;
+ case MENUACTION_SFXVOLUME:
+ DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsSfxVolume/128.0f);
+ break;
+ case MENUACTION_MOUSESENS:
+ DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), TheCamera.m_fMouseAccelHorzntl * 200.0f);
+ break;
+ };
+
+ // Radio icons.
+ float fIconSpacing = 59.52f;
+ if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) {
+ for (int i = 0; i < POLICE_RADIO; i++) {
+#ifndef ASPECT_RATIO_SCALE
+ if (i < USERTRACK)
+ m_aFrontEndSprites[i + FE_RADIO1].Draw(SCREEN_STRETCH_X(MENURADIO_ICON_X + fIconSpacing * i), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170));
+ if (i > CHATTERBOX && DMAudio.IsMP3RadioChannelAvailable())
+ m_aMenuSprites[MENUSPRITE_MP3LOGO].Draw(SCREEN_STRETCH_X(MENURADIO_ICON_X + fIconSpacing * i), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170));
+#else
+ float fMp3Pos = 0.0f;
+ if (DMAudio.IsMP3RadioChannelAvailable())
+ fMp3Pos = 34.0f;
+
+ if (i < USERTRACK)
+ m_aFrontEndSprites[i + FE_RADIO1].Draw((SCREEN_WIDTH * 0.5) + SCREEN_SCALE_X(-fMp3Pos + MENURADIO_ICON_X + (fIconSpacing * i)), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170));
+ if (i > CHATTERBOX && DMAudio.IsMP3RadioChannelAvailable())
+ m_aMenuSprites[MENUSPRITE_MP3LOGO].Draw((SCREEN_WIDTH * 0.5) + SCREEN_SCALE_X(-fMp3Pos + MENURADIO_ICON_X + (fIconSpacing * i)), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170));
+#endif
+ }
+ }
+
+ // Helpers
+ if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES")) {
+ if (m_nDisplayVideoMode == m_nPrefsVideoMode) {
+ if (m_nHelperTextMsgId == 1)
+ ResetHelperText();
+ }
+ else
+ SetHelperText(1);
+ }
+ else {
+ if (m_nDisplayVideoMode != m_nPrefsVideoMode) {
+ m_nDisplayVideoMode = m_nPrefsVideoMode;
+ SetHelperText(3);
+ }
+ }
+
+ switch (m_nCurrScreen) {
+ case MENUPAGE_CONTROLLER_SETTINGS:
+ case MENUPAGE_SOUND_SETTINGS:
+ case MENUPAGE_GRAPHICS_SETTINGS:
+ case MENUPAGE_SKIN_SELECT:
+ case MENUPAGE_CONTROLLER_PC:
+ case MENUPAGE_MOUSE_CONTROLS:
+ DisplayHelperText();
+ break;
+ };
+ }
+ };
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::DrawControllerBound(int, int, int, uint8) { EAXJMP(0x489710); }
+#else
+void CMenuManager::DrawControllerBound(int, int, int, uint8)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::DrawControllerScreenExtraText(int, int, int) { EAXJMP(0x4892F0); }
+#else
+void CMenuManager::DrawControllerScreenExtraText(int, int, int)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::DrawControllerSetupScreen() { EAXJMP(0x481210); }
+#else
+void CMenuManager::DrawControllerSetupScreen()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::DrawFrontEnd(void) { EAXJMP(0x47A540); }
+#else
+void CMenuManager::DrawFrontEnd()
+{
+ CFont::SetAlphaFade(255.0f);
+
+ if (m_nCurrScreen == MENUPAGE_NONE) {
+ m_nMenuFadeAlpha = 0;
+
+ if (m_bGameNotLoaded)
+ m_nCurrScreen = MENUPAGE_START_MENU;
+ else
+ m_nCurrScreen = MENUPAGE_PAUSE_MENU;
+ }
+
+ if (!m_nCurrOption && aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL)
+ m_nCurrOption = MENUROW_1;
+
+ CMenuManager::DrawFrontEndNormal();
+ CMenuManager::PrintErrorMessage();
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::DrawFrontEndNormal(void) { EAXJMP(0x47A5B0); }
+#else
+void CMenuManager::DrawFrontEndNormal()
+{
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
+ RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSCLAMP);
+
+ CSprite2d::InitPerFrame();
+ CFont::InitPerFrame();
+
+ eMenuSprites previousSprite = MENUSPRITE_MAINMENU;
+ if (m_nMenuFadeAlpha < 255) {
+ switch (m_nPrevScreen) {
+ case MENUPAGE_STATS:
+ case MENUPAGE_START_MENU:
+ case MENUPAGE_PAUSE_MENU:
+ previousSprite = MENUSPRITE_MAINMENU;
+ break;
+ case MENUPAGE_NEW_GAME:
+ case MENUPAGE_CHOOSE_LOAD_SLOT:
+ case MENUPAGE_CHOOSE_DELETE_SLOT:
+ case MENUPAGE_NEW_GAME_RELOAD:
+ case MENUPAGE_LOAD_SLOT_CONFIRM:
+ case MENUPAGE_DELETE_SLOT_CONFIRM:
+ case MENUPAGE_EXIT:
+ previousSprite = MENUSPRITE_SINGLEPLAYER;
+ break;
+ case MENUPAGE_MULTIPLAYER_MAIN:
+ previousSprite = MENUSPRITE_MULTIPLAYER;
+ break;
+ case MENUPAGE_MULTIPLAYER_MAP:
+ case MENUPAGE_MULTIPLAYER_FIND_GAME:
+ case MENUPAGE_SKIN_SELECT:
+ case MENUPAGE_KEYBOARD_CONTROLS:
+ case MENUPAGE_MOUSE_CONTROLS:
+ previousSprite = MENUSPRITE_FINDGAME;
+ break;
+ case MENUPAGE_MULTIPLAYER_CONNECTION:
+ case MENUPAGE_MULTIPLAYER_MODE:
+ previousSprite = MENUSPRITE_CONNECTION;
+ break;
+ case MENUPAGE_MULTIPLAYER_CREATE:
+ previousSprite = MENUSPRITE_HOSTGAME;
+ break;
+ case MENUPAGE_SKIN_SELECT_OLD:
+ case MENUPAGE_OPTIONS:
+ previousSprite = MENUSPRITE_PLAYERSET;
+ break;
+ };
+
+ if (m_nPrevScreen == MENUPAGE_NONE)
+ CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255));
+ else
+ m_aMenuSprites[previousSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255));
+ }
+
+ eMenuSprites currentSprite = MENUSPRITE_MAINMENU;
+ switch (m_nCurrScreen) {
+ case MENUPAGE_STATS:
+ case MENUPAGE_START_MENU:
+ case MENUPAGE_PAUSE_MENU:
+ currentSprite = MENUSPRITE_MAINMENU;
+ break;
+ case MENUPAGE_NEW_GAME:
+ case MENUPAGE_CHOOSE_LOAD_SLOT:
+ case MENUPAGE_CHOOSE_DELETE_SLOT:
+ case MENUPAGE_NEW_GAME_RELOAD:
+ case MENUPAGE_LOAD_SLOT_CONFIRM:
+ case MENUPAGE_DELETE_SLOT_CONFIRM:
+ case MENUPAGE_EXIT:
+ currentSprite = MENUSPRITE_SINGLEPLAYER;
+ break;
+ case MENUPAGE_MULTIPLAYER_MAIN:
+ currentSprite = MENUSPRITE_MULTIPLAYER;
+ break;
+ case MENUPAGE_MULTIPLAYER_MAP:
+ case MENUPAGE_MULTIPLAYER_FIND_GAME:
+ case MENUPAGE_SKIN_SELECT:
+ case MENUPAGE_KEYBOARD_CONTROLS:
+ case MENUPAGE_MOUSE_CONTROLS:
+ currentSprite = MENUSPRITE_FINDGAME;
+ break;
+ case MENUPAGE_MULTIPLAYER_CONNECTION:
+ case MENUPAGE_MULTIPLAYER_MODE:
+ currentSprite = MENUSPRITE_CONNECTION;
+ break;
+ case MENUPAGE_MULTIPLAYER_CREATE:
+ currentSprite = MENUSPRITE_HOSTGAME;
+ break;
+ case MENUPAGE_SKIN_SELECT_OLD:
+ case MENUPAGE_OPTIONS:
+ currentSprite = MENUSPRITE_PLAYERSET;
+ break;
+ };
+
+ uint32 savedShade;
+ uint32 savedAlpha;
+ RwRenderStateGet(rwRENDERSTATESHADEMODE, &savedShade);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, reinterpret_cast<void *>(rwSHADEMODEGOURAUD));
+ RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, &savedAlpha);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast<void *>(TRUE));
+ if (m_nMenuFadeAlpha >= 255) {
+ m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255));
+ }
+ else {
+ if (m_nMenuFadeAlpha < 255) {
+ m_nMenuFadeAlpha += 0.1f * 255.0f;
+
+ if (m_nMenuFadeAlpha >= 255)
+ m_nMenuFadeAlpha = 255;
+
+ m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, m_nMenuFadeAlpha));
+ }
+ else
+ m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255));
+ }
+
+ // GTA LOGO
+ if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_PAUSE_MENU) {
+ if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame)
+ m_aMenuSprites[MENUSPRITE_GTA3LOGO].Draw(CRect((SCREEN_WIDTH / 2) - SCREEN_SCALE_X(115.0f), SCREEN_SCALE_Y(70.0f), (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(115.0f), SCREEN_SCALE_Y(180.0f)), CRGBA(255, 255, 255, FadeIn(255)));
+ else
+ m_aMenuSprites[MENUSPRITE_GTALOGO].Draw(CRect((SCREEN_WIDTH / 2) - SCREEN_SCALE_X(95.0f), SCREEN_SCALE_Y(40.0f), (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(95.0f), SCREEN_SCALE_Y(210.0f)), CRGBA(255, 255, 255, FadeIn(255)));
+ }
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, reinterpret_cast<void *>(savedShade));
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast<void *>(savedAlpha));
+
+ switch (m_nCurrScreen) {
+ case MENUPAGE_SKIN_SELECT:
+ CMenuManager::DrawPlayerSetupScreen();
+ break;
+ case MENUPAGE_KEYBOARD_CONTROLS:
+ CMenuManager::DrawControllerSetupScreen();
+ break;
+ default:
+ CMenuManager::Draw();
+ break;
+ };
+
+ CFont::DrawFonts();
+
+ // Draw mouse
+ if (m_bShowMouse)
+ m_aMenuSprites[MENUSPRITE_MOUSE].Draw(m_nMousePosX, m_nMousePosY, SCREEN_SCALE_X(60.0f), SCREEN_SCALE_Y(60.0f), CRGBA(255, 255, 255, 255));
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::DrawPlayerSetupScreen() { EAXJMP(0x47F2B0); }
+#else
+void CMenuManager::DrawPlayerSetupScreen()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER int CMenuManager::FadeIn(int alpha) { EAXJMP(0x48AC60); }
+#else
+int CMenuManager::FadeIn(int alpha)
+{
+ if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS ||
+ m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS ||
+ m_nCurrScreen == MENUPAGE_DELETING)
+ return alpha;
+
+ if (m_nMenuFadeAlpha >= alpha)
+ return alpha;
+
+ return m_nMenuFadeAlpha;
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::FilterOutColorMarkersFromString(uint16, CRGBA &) { EAXJMP(0x4889C0); }
+#else
+void CMenuManager::FilterOutColorMarkersFromString(uint16, CRGBA &)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER int CMenuManager::GetStartOptionsCntrlConfigScreens() { EAXJMP(0x489270); }
+#else
+int CMenuManager::GetStartOptionsCntrlConfigScreens()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::InitialiseChangedLanguageSettings() { EAXJMP(0x47A4D0); }
+#else
+void CMenuManager::InitialiseChangedLanguageSettings()
+{
+ if (m_bFrontEnd_ReloadObrTxtGxt) {
+ m_bFrontEnd_ReloadObrTxtGxt = false;
+ CTimer::Stop();
+ TheText.Unload();
+ TheText.Load();
+ CTimer::Update();
+ CGame::frenchGame = false;
+ CGame::germanGame = false;
+ switch (CMenuManager::m_PrefsLanguage) {
+ case LANGUAGE_FRENCH:
+ CGame::frenchGame = true;
+ break;
+ case LANGUAGE_GERMAN:
+ CGame::germanGame = true;
+ break;
+ default:
+ break;
+ };
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::LoadAllTextures() { EAXJMP(0x47A230); }
+#else
+void CMenuManager::LoadAllTextures()
+{
+ if (!m_bSpritesLoaded) {
+ CMenuManager::CentreMousePointer();
+ DMAudio.ChangeMusicMode(0);
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0);
+ m_nCurrOption = MENUROW_0;
+ m_PrefsRadioStation = DMAudio.GetRadioInCar();
+
+ if (DMAudio.IsMP3RadioChannelAvailable()) {
+ if (CMenuManager::m_PrefsRadioStation > USERTRACK)
+ CMenuManager::m_PrefsRadioStation = CGeneral::GetRandomNumber() % 10;
+ }
+ else if (CMenuManager::m_PrefsRadioStation > CHATTERBOX)
+ CMenuManager::m_PrefsRadioStation = CGeneral::GetRandomNumber() % 9;
+
+ CFileMgr::SetDir("");
+ CTimer::Stop();
+ CStreaming::MakeSpaceFor(700 * 1024);
+ CStreaming::ImGonnaUseStreamingMemory();
+ CTxdStore::PushCurrentTxd();
+
+ int frontend = CTxdStore::AddTxdSlot("frontend");
+ CTxdStore::LoadTxd(frontend, "MODELS/FRONTEND.TXD");
+ CTxdStore::AddRef(frontend);
+ CTxdStore::SetCurrentTxd(frontend);
+ CStreaming::IHaveUsedStreamingMemory();
+ CTimer::Update();
+
+ debug("LOAD frontend\n");
+ for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); i++) {
+ m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i]);
+ m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER);
+ };
+
+ CTxdStore::PopCurrentTxd();
+
+ int menu = CTxdStore::AddTxdSlot("menu");
+ CTxdStore::LoadTxd(menu, "MODELS/MENU.TXD");
+ CTxdStore::AddRef(menu);
+ CTxdStore::SetCurrentTxd(menu);
+
+ debug("LOAD sprite\n");
+ for (int i = 0; i < ARRAY_SIZE(MenuFilenames)/2; i++) {
+ m_aMenuSprites[i].SetTexture(MenuFilenames[i*2], MenuFilenames[i*2+1]);
+ m_aMenuSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER);
+ };
+
+ CTxdStore::PopCurrentTxd();
+
+ m_bSpritesLoaded = true;
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::LoadSettings() { EAXJMP(0x488EE0); }
+#else
+void CMenuManager::LoadSettings()
+{
+
+ CFileMgr::SetDirMyDocuments();
+
+ uint8 prevLang = m_PrefsLanguage;
+ MousePointerStateHelper.bInvertVertically = true;
+
+ static char Ver;
+ int fileHandle = CFileMgr::OpenFile("gta3.set", "r");
+ if (fileHandle) {
+ CFileMgr::Read(fileHandle, buf(&Ver), sizeof(Ver));
+
+ if (strncmp(&Ver, "THIS FILE IS NOT VALID YET", 26)) {
+ CFileMgr::Seek(fileHandle, 0, 0);
+ ControlsManager.LoadSettings(fileHandle);
+ CFileMgr::Read(fileHandle, buf(&gString), 20);
+ CFileMgr::Read(fileHandle, buf(&gString), 20);
+ CFileMgr::Read(fileHandle, buf(&gString), 4);
+ CFileMgr::Read(fileHandle, buf(&gString), 4);
+ CFileMgr::Read(fileHandle, buf(&gString), 1);
+ CFileMgr::Read(fileHandle, buf(&gString), 1);
+ CFileMgr::Read(fileHandle, buf(&gString), 1);
+ CFileMgr::Read(fileHandle, buf(&TheCamera.m_bHeadBob), 1);
+ CFileMgr::Read(fileHandle, buf(&TheCamera.m_fMouseAccelHorzntl), 4);
+ CFileMgr::Read(fileHandle, buf(&TheCamera.m_fMouseAccelVertical), 4);
+ CFileMgr::Read(fileHandle, buf(&MousePointerStateHelper.bInvertVertically), 1);
+ CFileMgr::Read(fileHandle, buf(&CVehicle::m_bDisableMouseSteering), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsSfxVolume), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsMusicVolume), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsRadioStation), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsSpeakers), 1);
+ CFileMgr::Read(fileHandle, buf(&m_nPrefsAudio3DProviderIndex), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsDMA), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsBrightness), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsLOD), 4);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsShowSubtitles), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsUseWideScreen), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsVsyncDisp), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsFrameLimiter), 1);
+ CFileMgr::Read(fileHandle, buf(&m_nDisplayVideoMode), 1);
+ CFileMgr::Read(fileHandle, buf(&CMBlur::BlurOn), 1);
+ CFileMgr::Read(fileHandle, buf(m_PrefsSkinFile), 256);
+ CFileMgr::Read(fileHandle, buf(&m_ControlMethod), 1);
+ CFileMgr::Read(fileHandle, buf(&m_PrefsLanguage), 1);
+ }
+ }
+
+ CFileMgr::CloseFile(fileHandle);
+ CFileMgr::SetDir("");
+
+ m_PrefsVsync = m_PrefsVsyncDisp;
+ lodMultiplier = m_PrefsLOD;
+
+ if (m_nPrefsAudio3DProviderIndex == -1)
+ m_nPrefsAudio3DProviderIndex = -2;
+
+ if (m_PrefsLanguage == prevLang)
+ m_bLanguageLoaded = false;
+ else {
+ m_bLanguageLoaded = true;
+ TheText.Unload();
+ TheText.Load();
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+
+ debug("The previously saved language is now in use");
+ }
+
+ /*struct _WIN32_FIND_DATAA FindFileData;
+ HANDLE H = FindFirstFileA("skins\*.bmp", &FindFileData);
+ char Dest;
+ bool SkinFound = false;
+
+ for (int i = 1; H != (HANDLE)-1 && i; i = FindNextFileA(H, &FindFileData)) {
+ strcpy(&Dest, buf(m_PrefsSkinFile));
+ strcat(&Dest, ".bmp");
+ if (!strcmp(FindFileData.cFileName, &Dest))
+ SkinFound = true;
+ }
+
+ FindClose(H);
+
+ if (!SkinFound) {
+ debug("Default skin set as no other skins are available OR saved skin not found!");
+ strcpy((char *)CMenuManager::m_PrefsSkinFile, "$$\"\"");
+ strcpy(m_aSkinName, "$$\"\"");
+ }*/
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::MessageScreen(char *) { EAXJMP(0x48B7E0); }
+#else
+void CMenuManager::MessageScreen(char *)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::PickNewPlayerColour() { EAXJMP(0x488C40); }
+#else
+void CMenuManager::PickNewPlayerColour()
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::PrintBriefs() { EAXJMP(0x484D60); }
+#else
+void CMenuManager::PrintBriefs()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::PrintErrorMessage() { EAXJMP(0x484F70); }
+#else
+void CMenuManager::PrintErrorMessage()
+{
+ if (!CPad::bDisplayNoControllerMessage && !CPad::bObsoleteControllerMessage)
+ return;
+
+ CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(140.0f), SCREEN_WIDTH - SCREEN_SCALE_X(20.0f), SCREEN_HEIGHT - SCREEN_SCALE_Y(140.0f)), CRGBA(64, 16, 16, 224));
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::SetBackgroundOff();
+ CFont::SetPropOn();
+ CFont::SetCentreOff();
+ CFont::SetJustifyOn();
+ CFont::SetRightJustifyOff();
+ CFont::SetBackGroundOnlyTextOn();
+ CFont::SetWrapx(SCREEN_WIDTH - 40.0f);
+ CFont::SetColor(CRGBA(165, 165, 165, 255));
+ CFont::SetScale(SCREEN_SCALE_X(0.9f), SCREEN_SCALE_Y(0.9f));
+ CFont::PrintString(SCREEN_SCALE_X(40.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(60.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT"));
+ CFont::DrawFonts();
+}
+#endif
+
+#if 1
+WRAPPER void CMenuManager::PrintStats() { EAXJMP(0x482100); }
+#else
+void CMenuManager::PrintStats()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::Process(void) { EAXJMP(0x485100); }
+#else
+void CMenuManager::Process(void)
+{
+ if (m_bSaveMenuActive && TheCamera.GetScreenFadeStatus())
+ return;
+
+ field_113 = 0;
+ InitialiseChangedLanguageSettings();
+
+ SwitchMenuOnAndOff();
+
+ if (m_bMenuActive) {
+ LoadAllTextures();
+
+ if (m_nCurrScreen == MENUPAGE_DELETING) {
+ bool SlotPopulated = false;
+
+ if (PcSaveHelper.DeleteSlot(m_nCurrSaveSlot)) {
+ PcSaveHelper.PopulateSlotInfo();
+ SlotPopulated = true;
+ }
+
+ if (SlotPopulated) {
+ m_nPrevScreen = m_nCurrScreen;
+ m_nCurrScreen = MENUPAGE_DELETE_SUCCESS;
+ m_nCurrOption = 0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ }
+ else
+ SaveLoadFileError_SetUpErrorScreen();
+ }
+ if (m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS) {
+ int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot);
+ PcSaveHelper.PopulateSlotInfo();
+ if (SaveSlot) {
+ m_nPrevScreen = m_nCurrScreen;
+ m_nCurrScreen = MENUPAGE_SAVE_SUCCESSFUL;
+ m_nCurrOption = 0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ }
+ else
+ SaveLoadFileError_SetUpErrorScreen();
+ }
+ if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS) {
+ if (CheckSlotDataValid(m_nCurrSaveSlot)) {
+ TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == 0;
+ if (m_PrefsVsyncDisp != m_PrefsVsync)
+ m_PrefsVsync = m_PrefsVsyncDisp;
+ DMAudio.Service();
+ m_bStartGameLoading = 1;
+ RequestFrontEndShutdown();
+ m_bLoadingSavedGame = 1;
+ b_FoundRecentSavedGameWantToLoad = 1;
+ DMAudio.SetEffectsFadeVol(0);
+ DMAudio.SetMusicFadeVol(0);
+ DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds());
+ }
+ else
+ SaveLoadFileError_SetUpErrorScreen();
+ }
+
+ ProcessButtonPresses();
+ }
+ else {
+ if (GetPadExitEnter())
+ RequestFrontEndStartUp();
+
+ UnloadTextures();
+ m_nPrevScreen = MENUPAGE_NONE;
+ m_nCurrScreen = m_nPrevScreen;
+ m_nCurrOption = MENUROW_0;
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::ProcessButtonPresses() { EAXJMP(0x4856F0); }
+#else
+void CMenuManager::ProcessButtonPresses()
+{
+ // Update Mouse Position
+ m_nMouseOldPosX = m_nMousePosX;
+ m_nMouseOldPosY = m_nMousePosY;
+
+ m_nMousePosX = m_nMouseTempPosX;
+ m_nMousePosY = m_nMouseTempPosY;
+
+ if (m_nMousePosX < 0)
+ m_nMousePosX = 0;
+ if (m_nMousePosX > SCREEN_WIDTH)
+ m_nMousePosX = SCREEN_WIDTH;
+ if (m_nMousePosY < 0)
+ m_nMousePosY = 0;
+ if (m_nMousePosY > SCREEN_HEIGHT)
+ m_nMousePosY = SCREEN_HEIGHT;
+
+ // Show/hide mouse cursor.
+ if (GetMouseInput())
+ m_bShowMouse = true;
+ else if (GetPadInput())
+ m_bShowMouse = false;
+
+ // Get number of menu options.
+ uint8 NumberOfMenuOptions = GetNumberOfMenuOptions();
+
+ // Select next/previous option with pad. Mouse is done in drawing function.
+ if (GetPadMoveUp()) {
+ m_nPrevOption = m_nCurrOption;
+ m_nCurrOption -= 1;
+
+ if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) {
+ if (m_nCurrOption < MENUROW_1)
+ m_nCurrOption = NumberOfMenuOptions;
+ }
+ else {
+ if (m_nCurrOption < MENUROW_0)
+ m_nCurrOption = NumberOfMenuOptions;
+ }
+
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0);
+ }
+ else if (GetPadMoveDown()) {
+ m_nPrevOption = m_nCurrOption;
+ m_nCurrOption += 1;
+
+ if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) {
+ if (m_nCurrOption > NumberOfMenuOptions)
+ m_nCurrOption = MENUROW_1;
+ }
+ else {
+ if (m_nCurrOption > NumberOfMenuOptions)
+ m_nCurrOption = MENUROW_0;
+ }
+
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0);
+ }
+
+ // Set what happens if ESC is pressed.
+ if (GetPadBack()) {
+ bool PlayEscSound = false;
+ switch (m_nCurrScreen) {
+ case MENUPAGE_START_MENU:
+ break;
+ case MENUPAGE_CHOOSE_SAVE_SLOT:
+ case MENUPAGE_PAUSE_MENU:
+ RequestFrontEndShutdown();
+ PlayEscSound = true;
+ break;
+ default:
+ SwitchToNewScreen(aScreens[m_nCurrScreen].m_PreviousPage[0]);
+ PlayEscSound = true;
+ break;
+ };
+
+ if (PlayEscSound)
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0);
+ }
+
+ // TODO: finish hover options.
+ // Set mouse buttons.
+ if (GetMouseForward()) {
+ switch (m_nHoverOption) {
+ case ACTIVATE_OPTION:
+ if (m_nCurrOption || m_nCurrScreen != MENUPAGE_PAUSE_MENU)
+ m_nCurrOption = m_nPrevOption;
+
+ m_nHoverOption = ACTIVATE_OPTION;
+ break;
+ default:
+ break;
+ };
+ }
+
+ // Process all menu options here, but first check if it's an option or a redirect.
+ int32 CurrAction = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action;
+ if ((GetPadForward() || GetMouseForward()) ||
+ ((GetPadMoveLeft() || GetMouseMoveRight()) || (GetPadMoveRight() || GetMouseMoveLeft())) &&
+ (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu == m_nCurrScreen &&
+ CurrAction != MENUACTION_CHANGEMENU &&
+ CurrAction != MENUACTION_LOADRADIO &&
+ CurrAction != MENUACTION_RESTOREDEF &&
+ CurrAction != MENUACTION_PLAYERSETUP)) {
+
+ if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB"))
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0);
+ else
+ DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SUCCESS, 0);
+
+ ProcessOnOffMenuOptions();
+ }
+
+ // Process screens that may redirect you somewhere, or may not.
+ switch (m_nCurrScreen) {
+ case MENUPAGE_LOAD_SLOT_CONFIRM:
+ break;
+ case MENUPAGE_NEW_GAME_RELOAD:
+ if (m_bGameNotLoaded)
+ DoSettingsBeforeStartingAGame();
+ break;
+ case MENUPAGE_CHOOSE_DELETE_SLOT:
+ case MENUPAGE_CHOOSE_SAVE_SLOT:
+ case MENUPAGE_CHOOSE_LOAD_SLOT:
+ PcSaveHelper.PopulateSlotInfo();
+ break;
+ default:
+ break;
+ };
+
+ // Reset pad shaking.
+ if (VibrationTime != 0) {
+ if (CTimer::GetTimeInMillisecondsPauseMode() > VibrationTime) {
+ CPad::GetPad(0)->StopShaking(0);
+ VibrationTime = 0;
+ }
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::ProcessOnOffMenuOptions() { EAXJMP(0x48AE60); }
+#else
+void CMenuManager::ProcessOnOffMenuOptions()
+{
+ int8 InputDirection = (GetPadMoveLeft() || GetMouseMoveLeft()) && (!GetPadForward() && !GetMouseForward()) ? -1 : 1;
+ int8 InputEnter = GetPadForward();
+
+ uint8 NumberOfMenuOptions = GetNumberOfMenuOptions();
+
+ // In numerical order.
+ switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) {
+ case MENUACTION_CHANGEMENU:
+ SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu);
+ break;
+ case MENUACTION_CTRLVIBRATION:
+ if (!m_PrefsUseVibration)
+ m_PrefsUseVibration = true;
+
+ if (m_PrefsUseVibration) {
+ CPad::GetPad(0)->StartShake(350, 150);
+ VibrationTime = CTimer::GetTimeInMillisecondsPauseMode() + 500;
+ }
+ SaveSettings();
+ break;
+ case MENUACTION_FRAMESYNC:
+ m_PrefsVsync = m_PrefsVsync == false;
+ SaveSettings();
+ break;
+ case MENUACTION_FRAMELIMIT:
+ m_PrefsFrameLimiter = m_PrefsFrameLimiter == false;
+ SaveSettings();
+ break;
+ case MENUACTION_TRAILS:
+ CMBlur::BlurOn = CMBlur::BlurOn == false;
+ if (CMBlur::BlurOn)
+ CMBlur::MotionBlurOpen(Scene.camera);
+ else
+ CMBlur::MotionBlurClose();
+
+ SaveSettings();
+ break;
+ case MENUACTION_SUBTITLES:
+ m_PrefsShowSubtitles = m_PrefsShowSubtitles == false;
+ SaveSettings();
+ break;
+ case MENUACTION_WIDESCREEN:
+#ifndef ASPECT_RATIO_SCALE
+ m_PrefsUseWideScreen = m_PrefsUseWideScreen == false;
+#else
+ if (InputDirection > 0) {
+ switch (m_PrefsUseWideScreen) {
+ case AR_AUTO:
+ m_PrefsUseWideScreen = AR_4_3;
+ break;
+ case AR_4_3:
+ m_PrefsUseWideScreen = AR_16_9;
+ break;
+ case AR_16_9:
+ m_PrefsUseWideScreen = AR_AUTO;
+ break;
+ };
+ }
+ else {
+ switch (m_PrefsUseWideScreen) {
+ case AR_AUTO:
+ m_PrefsUseWideScreen = AR_16_9;
+ break;
+ case AR_4_3:
+ m_PrefsUseWideScreen = AR_AUTO;
+ break;
+ case AR_16_9:
+ m_PrefsUseWideScreen = AR_4_3;
+ break;
+ };
+ }
+#endif
+ SaveSettings();
+ break;
+ case MENUACTION_BRIGHTNESS:
+ case MENUACTION_DRAWDIST:
+ case MENUACTION_MUSICVOLUME:
+ case MENUACTION_SFXVOLUME:
+ case MENUACTION_MOUSESENS:
+ if (InputDirection > 0)
+ CheckSliderMovement(1.0f);
+ else
+ CheckSliderMovement(-1.0f);
+ break;
+ case MENUACTION_RADIO:
+ if (InputDirection < 0)
+ m_PrefsRadioStation -= 1;
+ else
+ m_PrefsRadioStation += 1;
+
+ if (DMAudio.IsMP3RadioChannelAvailable()) {
+ if (m_PrefsRadioStation > USERTRACK)
+ m_PrefsRadioStation = HEAD_RADIO;
+ else if (m_PrefsRadioStation < HEAD_RADIO)
+ m_PrefsRadioStation = USERTRACK;
+ }
+ else {
+ if (m_PrefsRadioStation > CHATTERBOX)
+ m_PrefsRadioStation = HEAD_RADIO;
+ else if (m_PrefsRadioStation < HEAD_RADIO)
+ m_PrefsRadioStation = CHATTERBOX;
+ }
+
+ SaveSettings();
+ DMAudio.SetRadioInCar(m_PrefsRadioStation);
+ DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, 1);
+ break;
+ case MENUACTION_LANG_ENG:
+ if (m_PrefsLanguage != LANGUAGE_AMERICAN) {
+ m_PrefsLanguage = LANGUAGE_AMERICAN;
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+ SaveSettings();
+ }
+ break;
+ case MENUACTION_LANG_FRE:
+ if (m_PrefsLanguage != LANGUAGE_FRENCH) {
+ m_PrefsLanguage = LANGUAGE_FRENCH;
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+ SaveSettings();
+ }
+ break;
+ case MENUACTION_LANG_GER:
+ if (m_PrefsLanguage != LANGUAGE_GERMAN) {
+ m_PrefsLanguage = LANGUAGE_GERMAN;
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+ SaveSettings();
+ }
+ break;
+ case MENUACTION_LANG_ITA:
+ if (m_PrefsLanguage != LANGUAGE_ITALIAN) {
+ m_PrefsLanguage = LANGUAGE_ITALIAN;
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+ SaveSettings();
+ }
+ break;
+ case MENUACTION_LANG_SPA:
+ if (m_PrefsLanguage != LANGUAGE_SPANISH) {
+ m_PrefsLanguage = LANGUAGE_SPANISH;
+ m_bFrontEnd_ReloadObrTxtGxt = true;
+ InitialiseChangedLanguageSettings();
+ SaveSettings();
+ }
+ break;
+ case MENUACTION_UPDATESAVE:
+ PcSaveHelper.PopulateSlotInfo();
+ if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrOption].m_aEntries[m_nCurrOption].m_SaveSlot <= SAVESLOT_8) {
+ m_nCurrSaveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 2;
+
+ SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu);
+ }
+ break;
+ case MENUACTION_CHECKSAVE:
+ if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrOption].m_aEntries[m_nCurrOption].m_SaveSlot <= SAVESLOT_8) {
+ m_nCurrSaveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 2;
+
+ if (Slots[m_nCurrSaveSlot] != 1 && Slots[m_nCurrSaveSlot] != 2)
+ SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu);
+ }
+ break;
+ case MENUACTION_NEWGAME:
+ DoSettingsBeforeStartingAGame();
+ break;
+ case MENUACTION_SETDBGFLAG:
+ CTheScripts::DbgFlag = CTheScripts::DbgFlag == false;
+ break;
+ case MENUACTION_UPDATEMEMCARDSAVE:
+ RequestFrontEndShutdown();
+ break;
+ case MENUACTION_INVVERT:
+ MousePointerStateHelper.bInvertVertically = MousePointerStateHelper.bInvertVertically == false;
+ return;
+ case MENUACTION_CANCLEGAME:
+ DMAudio.Service();
+ RsEventHandler(rsQUITAPP, 0);
+ break;
+ case MENUACTION_RESUME:
+ RequestFrontEndShutdown();
+ break;
+ case MENUACTION_SCREENRES:
+ if (m_bGameNotLoaded) {
+ if (InputEnter) {
+ if (m_nDisplayVideoMode != m_nPrefsVideoMode) {
+ m_nPrefsVideoMode = m_nDisplayVideoMode;
+ _psSelectScreenVM(m_nPrefsVideoMode);
+ CentreMousePointer();
+ m_PrefsUseWideScreen = false;
+ SaveSettings();
+ }
+ }
+ else {
+ char** VideoModeList = _psGetVideoModeList();
+ int NumVideoModes = _psGetNumVideModes();
+
+ if (InputDirection > 0) {
+ int nCurrentVidMode = m_nDisplayVideoMode + 1;
+
+ if (nCurrentVidMode >= NumVideoModes)
+ nCurrentVidMode = 0;
+
+ while (!VideoModeList[nCurrentVidMode]) {
+ ++nCurrentVidMode;
+
+ if (nCurrentVidMode >= NumVideoModes)
+ nCurrentVidMode = 0;
+ }
+
+ m_nDisplayVideoMode = nCurrentVidMode;
+ }
+ else {
+ int nCurrentVidMode = m_nDisplayVideoMode - 1;
+
+ if (nCurrentVidMode < 0)
+ nCurrentVidMode = NumVideoModes - 1;
+
+ while (!VideoModeList[nCurrentVidMode]) {
+ --nCurrentVidMode;
+
+ if (nCurrentVidMode < 0)
+ nCurrentVidMode = NumVideoModes - 1;
+ }
+
+ m_nDisplayVideoMode = nCurrentVidMode;
+ }
+ }
+ }
+ break;
+ case MENUACTION_AUDIOHW:
+ {
+ int8 AudioHardware = m_nPrefsAudio3DProviderIndex;
+ if (m_nPrefsAudio3DProviderIndex == -1)
+ break;
+
+ if (InputDirection > 0) {
+ switch (m_nPrefsAudio3DProviderIndex) {
+ case 0:
+ m_nPrefsAudio3DProviderIndex = 1;
+ break;
+ case 1:
+ m_nPrefsAudio3DProviderIndex = 2;
+ break;
+ case 2:
+ m_nPrefsAudio3DProviderIndex = 3;
+ break;
+ case 3:
+ m_nPrefsAudio3DProviderIndex = 4;
+ break;
+ case 4:
+ m_nPrefsAudio3DProviderIndex = 5;
+ break;
+ case 5:
+ m_nPrefsAudio3DProviderIndex = 6;
+ break;
+ case 6:
+ m_nPrefsAudio3DProviderIndex = 0;
+ break;
+ }
+ }
+ else {
+ switch (m_nPrefsAudio3DProviderIndex) {
+ case 0:
+ m_nPrefsAudio3DProviderIndex = 6;
+ break;
+ case 1:
+ m_nPrefsAudio3DProviderIndex = 0;
+ break;
+ case 2:
+ m_nPrefsAudio3DProviderIndex = 1;
+ break;
+ case 3:
+ m_nPrefsAudio3DProviderIndex = 2;
+ break;
+ case 4:
+ m_nPrefsAudio3DProviderIndex = 3;
+ break;
+ case 5:
+ m_nPrefsAudio3DProviderIndex = 4;
+ break;
+ case 6:
+ m_nPrefsAudio3DProviderIndex = 5;
+ break;
+ }
+ }
+
+ DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex);
+
+ if (AudioHardware == m_nPrefsAudio3DProviderIndex)
+ SetHelperText(0);
+ else
+ SetHelperText(4);
+
+ SaveSettings();
+ break;
+ }
+ case MENUACTION_SPEAKERCONF:
+ if (m_nPrefsAudio3DProviderIndex == -1)
+ break;
+
+ if (InputDirection > 0) {
+ switch (m_PrefsSpeakers) {
+ case 0:
+ m_PrefsSpeakers = 1;
+ break;
+ case 1:
+ m_PrefsSpeakers = 2;
+ break;
+ case 2:
+ m_PrefsSpeakers = 0;
+ break;
+ };
+ }
+ else {
+ switch (m_PrefsSpeakers) {
+ case 0:
+ m_PrefsSpeakers = 2;
+ break;
+ case 1:
+ m_PrefsSpeakers = 0;
+ break;
+ case 2:
+ m_PrefsSpeakers = 1;
+ break;
+ };
+ }
+
+ DMAudio.SetSpeakerConfig(m_PrefsSpeakers);
+ SaveSettings();
+ break;
+ case MENUACTION_RESTOREDEF:
+ SetDefaultPreferences(m_nCurrScreen);
+ SetHelperText(2);
+ SaveSettings();
+ break;
+ case MENUACTION_CTRLMETHOD:
+ if (m_ControlMethod) {
+ TheCamera.m_bUseMouse3rdPerson = 1;
+ m_ControlMethod = 0;
+ }
+ else {
+ TheCamera.m_bUseMouse3rdPerson = 0;
+ m_ControlMethod = 1;
+ }
+ SaveSettings();
+ break;
+ case MENUACTION_DYNAMICACOUSTIC:
+ m_PrefsDMA = m_PrefsDMA == false;
+ break;
+ case MENUACTION_MOUSESTEER:
+ m_bDisableMouseSteering = m_bDisableMouseSteering == false;
+ return;
+ };
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::RequestFrontEndShutdown() { EAXJMP(0x488750); }
+#else
+void CMenuManager::RequestFrontEndShutdown()
+{
+ m_bShutDownFrontEndRequested = true;
+ DMAudio.ChangeMusicMode(1);
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::RequestFrontEndStartUp() { EAXJMP(0x488770); }
+#else
+void CMenuManager::RequestFrontEndStartUp()
+{
+ m_bStartUpFrontEndRequested = 1;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::ResetHelperText() { EAXJMP(0x48B470); }
+#else
+void CMenuManager::ResetHelperText()
+{
+ m_nHelperTextMsgId = 0;
+ m_nHelperTextAlpha = 300;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::SaveLoadFileError_SetUpErrorScreen() { EAXJMP(0x488930); }
+#else
+void CMenuManager::SaveLoadFileError_SetUpErrorScreen()
+{
+ switch (PcSaveHelper.m_nHelper) {
+ case 1:
+ case 2:
+ case 3:
+ m_nPrevScreen = m_nCurrScreen;
+ m_nCurrScreen = MENUPAGE_SAVE_FAILED;
+ m_nCurrOption = MENUROW_0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ break;
+ break;
+ case 4:
+ case 5:
+ case 6:
+ this->m_nPrevScreen = m_nCurrScreen;
+ this->m_nCurrScreen = MENUPAGE_LOAD_FAILED;
+ m_nCurrOption = MENUROW_0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ break;
+ case 7:
+ this->m_nPrevScreen = m_nCurrScreen;
+ this->m_nCurrScreen = MENUPAGE_LOAD_FAILED_2;
+ m_nCurrOption = MENUROW_0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ break;
+ case 8:
+ case 9:
+ case 10:
+ m_nPrevScreen = m_nCurrScreen;
+ m_nCurrScreen = MENUPAGE_DELETE_FAILED;
+ m_nCurrOption = MENUROW_0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ break;
+ default:
+ return;
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::SetHelperText() { EAXJMP(0x48B450); }
+#else
+void CMenuManager::SetHelperText(int text)
+{
+ m_nHelperTextMsgId = text;
+ m_nHelperTextAlpha = 300;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::SaveSettings() { EAXJMP(0x488CC0); }
+#else
+void CMenuManager::SaveSettings()
+{
+ CFileMgr::SetDirMyDocuments();
+
+ int fileHandle = CFileMgr::OpenFile("gta3.set", "w");
+ if (fileHandle) {
+
+ ControlsManager.SaveSettings(fileHandle);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 20);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 20);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 4);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 4);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1);
+ CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1);
+ CFileMgr::Write(fileHandle, buf(&TheCamera.m_bHeadBob), 1);
+ CFileMgr::Write(fileHandle, buf(&TheCamera.m_fMouseAccelHorzntl), 4);
+ CFileMgr::Write(fileHandle, buf(&TheCamera.m_fMouseAccelVertical), 4);
+ CFileMgr::Write(fileHandle, buf(&MousePointerStateHelper.bInvertVertically), 1);
+ CFileMgr::Write(fileHandle, buf(&CVehicle::m_bDisableMouseSteering), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsSfxVolume), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsMusicVolume), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsRadioStation), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsSpeakers), 1);
+ CFileMgr::Write(fileHandle, buf(&m_nPrefsAudio3DProviderIndex), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsDMA), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsBrightness), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsLOD), sizeof(m_PrefsLOD));
+ CFileMgr::Write(fileHandle, buf(&m_PrefsShowSubtitles), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsUseWideScreen), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsVsyncDisp), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsFrameLimiter), 1);
+ CFileMgr::Write(fileHandle, buf(&m_nDisplayVideoMode), 1);
+ CFileMgr::Write(fileHandle, buf(&CMBlur::BlurOn), 1);
+ CFileMgr::Write(fileHandle, buf(m_PrefsSkinFile), 256);
+ CFileMgr::Write(fileHandle, buf(&m_ControlMethod), 1);
+ CFileMgr::Write(fileHandle, buf(&m_PrefsLanguage), 1);
+ }
+
+ CFileMgr::CloseFile(fileHandle);
+ CFileMgr::SetDir("");
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::ShutdownJustMenu() { EAXJMP(0x488920); }
+#else
+void CMenuManager::ShutdownJustMenu()
+{
+ m_bMenuActive = false;
+ CTimer::EndUserPause();
+}
+#endif
+
+// We won't ever use this again.
+#if 0
+WRAPPER float CMenuManager::StretchX(float) { EAXJMP(0x48ABE0); }
+#else
+float CMenuManager::StretchX(float x)
+{
+ if (SCREEN_WIDTH == 640)
+ return x;
+ else
+ return SCREEN_WIDTH * x * 0.0015625f;
+}
+#endif
+
+#if 0
+WRAPPER float CMenuManager::StretchY(float) { EAXJMP(0x48AC20); }
+#else
+float CMenuManager::StretchY(float y)
+{
+ if (SCREEN_HEIGHT == 448)
+ return y;
+ else
+ return SCREEN_HEIGHT * y * 0.002232143f;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::SwitchMenuOnAndOff() { EAXJMP(0x488790); }
+#else
+void CMenuManager::SwitchMenuOnAndOff()
+{
+ // Just what the function name says.
+ if (m_bShutDownFrontEndRequested || m_bStartUpFrontEndRequested) {
+ if (!m_bMenuActive)
+ m_bMenuActive = true;
+
+ if (m_bShutDownFrontEndRequested)
+ m_bMenuActive = false;
+ if (m_bStartUpFrontEndRequested)
+ m_bMenuActive = true;
+
+ if (m_bMenuActive) {
+ CTimer::StartUserPause();
+ }
+ else {
+ ShutdownJustMenu();
+ SaveSettings();
+ m_bStartUpFrontEndRequested = false;
+ pControlEdit = 0;
+ m_bShutDownFrontEndRequested = false;
+ DisplayComboButtonErrMsg = 0;
+ CPad::GetPad(0)->Clear(0);
+ CPad::GetPad(1)->Clear(0);
+ SwitchToNewScreen(0);
+ }
+ }
+ if (m_bSaveMenuActive && !m_bQuitGameNoCD) {
+ m_bSaveMenuActive = false;
+ m_bMenuActive = true;
+ CTimer::StartUserPause();
+ SwitchToNewScreen(MENUPAGE_CHOOSE_SAVE_SLOT);
+ PcSaveHelper.PopulateSlotInfo();
+ }
+
+ if (!m_bMenuActive)
+ field_112 = 1;
+
+ m_bStartUpFrontEndRequested = false;
+ m_bShutDownFrontEndRequested = false;
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::UnloadTextures() { EAXJMP(0x47A440); }
+#else
+void CMenuManager::UnloadTextures()
+{
+ if (m_bSpritesLoaded) {
+ debug("Remove frontend\n");
+ for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); ++i)
+ m_aFrontEndSprites[i].Delete();
+
+ int frontend = CTxdStore::FindTxdSlot("frontend");
+ CTxdStore::RemoveTxdSlot(frontend);
+
+ debug("Remove menu textures\n");
+ for (int i = 0; i < ARRAY_SIZE(MenuFilenames)/2; ++i)
+ m_aMenuSprites[i].Delete();
+
+ int menu = CTxdStore::FindTxdSlot("menu");
+ CTxdStore::RemoveTxdSlot(menu);
+
+ m_bSpritesLoaded = false;
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CMenuManager::WaitForUserCD(void) { EAXJMP(0x48ADD0); }
+#else
+void CMenuManager::WaitForUserCD()
+{
+ LoadSplash(0);
+ if (!RsGlobal.quit) {
+ HandleExit();
+ CPad::UpdatePads();
+ MessageScreen("NO_PCCD");
+
+ if (GetPadBack()) {
+ m_bQuitGameNoCD = true;
+ RsEventHandler(rsQUITAPP, 0);
+ }
+ }
+}
+#endif
+
+// New content:
+uint8 CMenuManager::GetNumberOfMenuOptions()
+{
+ uint8 Rows = MENUROW_NONE;
+ for (int i = 0; i < MENUROWS; i++) {
+ if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_NOTHING)
+ break;
+
+ ++Rows;
+ };
+ return Rows;
+}
+
+void CMenuManager::SwitchToNewScreen(int8 screen)
+{
+ ResetHelperText();
+
+ // Return to - behaviour.
+ if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") ||
+ (screen == aScreens[m_nCurrScreen].m_PreviousPage[0])) {
+ if (m_bGameNotLoaded) {
+ m_nCurrOption = aScreens[m_nCurrScreen].m_ParentEntry[0];
+ m_nCurrScreen = aScreens[m_nCurrScreen].m_PreviousPage[0];
+ }
+ else {
+ m_nCurrOption = aScreens[m_nCurrScreen].m_ParentEntry[1];
+ m_nCurrScreen = aScreens[m_nCurrScreen].m_PreviousPage[1];
+ }
+
+ m_nMenuFadeAlpha = 0;
+ }
+ else {
+ // Go through - behaviour.
+ if (screen) {
+ m_nPrevScreen = m_nCurrScreen;
+ m_nCurrScreen = screen;
+ m_nCurrOption = MENUROW_0;
+ m_nMenuFadeAlpha = 0;
+ m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode();
+ }
+ else {
+ m_nPrevScreen = MENUPAGE_NONE;
+ m_nCurrScreen = MENUPAGE_NONE;
+ m_nCurrOption = MENUROW_0;
+ }
+ }
+
+ // Set player skin.
+ if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) {
+ CPlayerSkin::BeginFrontEndSkinEdit();
+ field_535 = 19;
+ m_bSkinsFound = false;
+ }
+
+ // Set radio station.
+ if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) {
+ DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, 1);
+ OutputDebugStringA("FRONTEND AUDIO TRACK STOPPED");
+ }
+ else
+ DMAudio.StopFrontEndTrack();
+}
+
+void CMenuManager::SetDefaultPreferences(int8 screen)
+{
+ switch (screen) {
+ case MENUPAGE_SOUND_SETTINGS:
+ m_PrefsMusicVolume = 102;
+ m_PrefsSfxVolume = 102;
+ m_PrefsSpeakers = 0;
+ m_nPrefsAudio3DProviderIndex = 6;
+ m_PrefsDMA = true;
+ DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume);
+ DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume);
+ DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex);
+ break;
+ case MENUPAGE_GRAPHICS_SETTINGS:
+ m_PrefsBrightness = 256;
+ m_PrefsFrameLimiter = true;
+ m_PrefsVsync = true;
+ m_PrefsLOD = 1.2f;
+ m_PrefsVsyncDisp = true;
+ lodMultiplier = 1.2;
+ CMBlur::BlurOn = true;
+ CMBlur::MotionBlurOpen(Scene.camera);
+ m_PrefsUseVibration = false;
+ m_PrefsShowSubtitles = true;
+ m_nDisplayVideoMode = m_nPrefsVideoMode;
+ m_PrefsUseWideScreen = false;
+ break;
+ case MENUPAGE_CONTROLLER_PC:
+ ControlsManager.MakeControllerActionsBlank();
+ ControlsManager.InitDefaultControlConfiguration();
+
+ CMouseControllerState state = MousePointerStateHelper.GetMouseSetUp();
+ ControlsManager.InitDefaultControlConfigMouse(state);
+
+ if (1) {
+ //TODO: JoyPad stuff.
+ }
+ TheCamera.m_bUseMouse3rdPerson = 1;
+ m_ControlMethod = 0;
+ MousePointerStateHelper.bInvertVertically = true;
+ TheCamera.m_fMouseAccelHorzntl = 0.0025f;
+ TheCamera.m_fMouseAccelVertical = 0.0025f;
+ CVehicle::m_bDisableMouseSteering = true;
+ TheCamera.m_bHeadBob = false;
+ break;
+ };
+}
+
+// Frontend inputs.
+bool GetPadBack()
+{
+ return
+ (CPad::GetPad(0)->NewKeyState.ESC && !CPad::GetPad(0)->OldKeyState.ESC) ||
+ (CPad::GetPad(0)->NewState.Triangle && !CPad::GetPad(0)->OldState.Triangle);
+}
+
+bool GetPadExitEnter()
+{
+ return
+ (CPad::GetPad(0)->NewKeyState.ESC && !CPad::GetPad(0)->OldKeyState.ESC) ||
+ (CPad::GetPad(0)->NewState.Start && !CPad::GetPad(0)->OldState.Start);
+}
+
+bool GetPadForward()
+{
+ return
+ (CPad::GetPad(0)->NewKeyState.EXTENTER && !CPad::GetPad(0)->OldKeyState.EXTENTER) ||
+ (CPad::GetPad(0)->NewKeyState.ENTER && !CPad::GetPad(0)->OldKeyState.ENTER) ||
+ (CPad::GetPad(0)->NewState.Cross && !CPad::GetPad(0)->OldState.Cross);
+}
+
+bool GetPadMoveUp()
+{
+ return
+ (CPad::GetPad(0)->NewState.DPadUp && !CPad::GetPad(0)->OldState.DPadUp) ||
+ (CPad::GetPad(0)->NewKeyState.UP && !CPad::GetPad(0)->OldKeyState.UP) ||
+ (CPad::GetPad(0)->NewState.LeftStickY < 0 && !CPad::GetPad(0)->OldState.LeftStickY < 0);
+}
+
+bool GetPadMoveDown()
+{
+ return
+ (CPad::GetPad(0)->NewState.DPadDown && !CPad::GetPad(0)->OldState.DPadDown) ||
+ (CPad::GetPad(0)->NewKeyState.DOWN && !CPad::GetPad(0)->OldKeyState.DOWN) ||
+ (CPad::GetPad(0)->NewState.LeftStickY > 0 && !CPad::GetPad(0)->OldState.LeftStickY > 0);
+}
+
+bool GetPadMoveLeft()
+{
+ return
+ (CPad::GetPad(0)->NewState.DPadLeft && !CPad::GetPad(0)->OldState.DPadLeft) ||
+ (CPad::GetPad(0)->NewKeyState.LEFT && !CPad::GetPad(0)->OldKeyState.LEFT) ||
+ (CPad::GetPad(0)->NewState.LeftStickX < 0 && !CPad::GetPad(0)->OldState.LeftStickX < 0);
+}
+
+bool GetPadMoveRight()
+{
+ return
+ (CPad::GetPad(0)->NewState.DPadRight && !CPad::GetPad(0)->OldState.DPadRight) ||
+ (CPad::GetPad(0)->NewKeyState.RIGHT && !CPad::GetPad(0)->OldKeyState.RIGHT) ||
+ (CPad::GetPad(0)->NewState.LeftStickX > 0 && !CPad::GetPad(0)->OldState.LeftStickX > 0);
+}
+
+bool GetMouseForward()
+{
+ return
+ (CPad::GetPad(0)->NewMouseControllerState.LMB && !CPad::GetPad(0)->OldMouseControllerState.LMB);
+}
+
+bool GetMouseBack()
+{
+ return
+ (CPad::GetPad(0)->NewMouseControllerState.RMB && !CPad::GetPad(0)->OldMouseControllerState.RMB);
+}
+
+bool GetMousePos()
+{
+ return
+ (CPad::GetPad(0)->NewMouseControllerState.x != 0.0f || CPad::GetPad(0)->OldMouseControllerState.y != 0.0f);
+}
+
+bool GetMouseMoveLeft()
+{
+ return
+ (CPad::GetPad(0)->NewMouseControllerState.WHEELDN && !CPad::GetPad(0)->OldMouseControllerState.WHEELDN != 0.0f);
+}
+
+bool GetMouseMoveRight()
+{
+ return
+ (CPad::GetPad(0)->NewMouseControllerState.WHEELUP && !CPad::GetPad(0)->OldMouseControllerState.WHEELUP != 0.0f);
+}
+
+bool GetPadInput()
+{
+ return
+ GetPadBack() ||
+ GetPadForward() ||
+ GetPadMoveUp() ||
+ GetPadMoveDown() ||
+ GetPadMoveLeft() ||
+ GetPadMoveRight();
+}
+
+bool GetMouseInput()
+{
+ return
+ GetMouseForward() ||
+ GetMouseBack() ||
+ GetMousePos() ||
+ GetMouseMoveLeft() ||
+ GetMouseMoveRight();
+}
+
+STARTPATCHES
+ InjectHook(0x47A230, &CMenuManager::LoadAllTextures, PATCH_JUMP);
+ InjectHook(0x47A440, &CMenuManager::UnloadTextures, PATCH_JUMP);
+ InjectHook(0x485100, &CMenuManager::Process, PATCH_JUMP);
+ InjectHook(0x4856F0, &CMenuManager::ProcessButtonPresses, PATCH_JUMP);
+ InjectHook(0x48AE60, &CMenuManager::ProcessOnOffMenuOptions, PATCH_JUMP);
+ InjectHook(0x488EE0, &CMenuManager::LoadSettings, PATCH_JUMP);
+ InjectHook(0x488CC0, &CMenuManager::SaveSettings, PATCH_JUMP);
+
+ for (int i = 1; i < ARRAY_SIZE(aScreens); i++)
+ Patch(0x611930 + sizeof(CMenuScreen) * i, aScreens[i]);
+ENDPATCHES \ No newline at end of file
diff --git a/src/core/Frontend.h b/src/core/Frontend.h
new file mode 100644
index 00000000..9b9377da
--- /dev/null
+++ b/src/core/Frontend.h
@@ -0,0 +1,512 @@
+#pragma
+
+#include "Sprite2d.h"
+
+#define MENUHEADER_POS_X 35.0f
+#define MENUHEADER_POS_Y 93.0f
+#define MENUHEADER_WIDTH 0.84f
+#define MENUHEADER_HEIGHT 1.6f
+
+#define MENUACTION_POS_X 20.0f
+#define MENUACTION_POS_Y 37.5f
+#define MENUACTION_WIDTH 0.675f
+#define MENUACTION_HEIGHT 0.81f
+
+#define MENUCOLUMN_POS_X MENUHEADER_POS_X + 16.0f
+#define MENUCOLUMN_MAX_Y 149.0f
+#define MENUCOLUMN_MID_Y 100.0f
+#define MENUCOLUMN_MIN_Y 110.0f
+#define MENUCOLUMN_PAUSE_Y 25.0f
+#define MENUCOLUMN_START_Y 9.0f
+#define MENUCOLUMN_FEDS 139.0f
+
+#define MENUCOLUMN_SAVE_X 121.0f
+#define MENUCOLUMN_SAVE_Y 111.0f
+
+#define MENUCOLUMN_SPACING_MAX 24.0f
+#define MENUCOLUMN_SPACING_MIN 20.0f
+
+#define MENUSELECT_BOX_MAX 20.5f
+#define MENUSELECT_BOX_MIN 17.0f
+
+#ifndef ASPECT_RATIO_SCALE
+#define MENURADIO_ICON_X 31.5f
+#else
+#define MENURADIO_ICON_X -262.0f
+#endif
+#define MENURADIO_ICON_Y 29.5f
+#define MENURADIO_ICON_W 60.0f
+#define MENURADIO_ICON_H 60.0f
+
+#define MENUDROP_COLOR_A 150
+#define MENUDROP_COLOR_SIZE -1
+
+#define MENUSLIDER_X 306.0f
+
+#define buf(a) (char*)(a)
+
+enum eLanguages
+{
+ LANGUAGE_AMERICAN,
+ LANGUAGE_FRENCH,
+ LANGUAGE_GERMAN,
+ LANGUAGE_ITALIAN,
+ LANGUAGE_SPANISH,
+};
+
+enum eFrontendSprites
+{
+ FE2_MAINPANEL_UL,
+ FE2_MAINPANEL_UR,
+ FE2_MAINPANEL_DL,
+ FE2_MAINPANEL_DR,
+ FE2_MAINPANEL_DR2,
+ FE2_TABACTIVE,
+ FE_ICONBRIEF,
+ FE_ICONSTATS,
+ FE_ICONCONTROLS,
+ FE_ICONSAVE,
+ FE_ICONAUDIO,
+ FE_ICONDISPLAY,
+ FE_ICONLANGUAGE,
+ FE_CONTROLLER,
+ FE_CONTROLLERSH,
+ FE_ARROWS1,
+ FE_ARROWS2,
+ FE_ARROWS3,
+ FE_ARROWS4,
+ FE_RADIO1,
+ FE_RADIO2,
+ FE_RADIO3,
+ FE_RADIO4,
+ FE_RADIO5,
+ FE_RADIO6,
+ FE_RADIO7,
+ FE_RADIO8,
+ FE_RADIO9,
+};
+
+enum eMenuSprites
+{
+ MENUSPRITE_CONNECTION,
+ MENUSPRITE_FINDGAME,
+ MENUSPRITE_HOSTGAME,
+ MENUSPRITE_MAINMENU,
+ MENUSPRITE_PLAYERSET,
+ MENUSPRITE_SINGLEPLAYER,
+ MENUSPRITE_MULTIPLAYER,
+ MENUSPRITE_DMALOGO,
+ MENUSPRITE_GTALOGO,
+ MENUSPRITE_RSTARLOGO,
+ MENUSPRITE_GAMESPY,
+ MENUSPRITE_MOUSE,
+ MENUSPRITE_MOUSET,
+ MENUSPRITE_MP3LOGO,
+ MENUSPRITE_DOWNOFF,
+ MENUSPRITE_DOWNON,
+ MENUSPRITE_UPOFF,
+ MENUSPRITE_UPON,
+ MENUSPRITE_GTA3LOGO,
+};
+
+enum eSaveSlot
+{
+ SAVESLOT_NONE,
+ SAVESLOT_0,
+ SAVESLOT_1,
+ SAVESLOT_2,
+ SAVESLOT_3,
+ SAVESLOT_4,
+ SAVESLOT_5,
+ SAVESLOT_6,
+ SAVESLOT_7,
+ SAVESLOT_8,
+ SAVESLOT_LABEL = 36
+};
+
+enum eMenuScreen
+{
+ MENUPAGE_DISABLED = -1,
+ MENUPAGE_NONE = 0,
+ MENUPAGE_STATS = 1,
+ MENUPAGE_NEW_GAME = 2,
+ MENUPAGE_BRIEFS = 3,
+ MENUPAGE_CONTROLLER_SETTINGS = 4,
+ MENUPAGE_SOUND_SETTINGS = 5,
+ MENUPAGE_GRAPHICS_SETTINGS = 6,
+ MENUPAGE_LANGUAGE_SETTINGS = 7,
+ MENUPAGE_CHOOSE_LOAD_SLOT = 8,
+ MENUPAGE_CHOOSE_DELETE_SLOT = 9,
+ MENUPAGE_NEW_GAME_RELOAD = 10,
+ MENUPAGE_LOAD_SLOT_CONFIRM = 11,
+ MENUPAGE_DELETE_SLOT_CONFIRM = 12,
+ MENUPAGE_13 = 13,
+ MENUPAGE_LOADING_IN_PROGRESS = 14,
+ MENUPAGE_DELETING_IN_PROGRESS = 15,
+ MENUPAGE_16 = 16,
+ MENUPAGE_DELETE_FAILED = 17,
+ MENUPAGE_DEBUG_MENU = 18,
+ MENUPAGE_MEMORY_CARD_1 = 19,
+ MENUPAGE_MEMORY_CARD_2 = 20,
+ MENUPAGE_MULTIPLAYER_MAIN = 21,
+ MENUPAGE_SAVE_FAILED_1 = 22,
+ MENUPAGE_SAVE_FAILED_2 = 23,
+ MENUPAGE_SAVE = 24,
+ MENUPAGE_NO_MEMORY_CARD = 25,
+ MENUPAGE_CHOOSE_SAVE_SLOT = 26,
+ MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27,
+ MENUPAGE_MULTIPLAYER_MAP = 28,
+ MENUPAGE_MULTIPLAYER_CONNECTION = 29,
+ MENUPAGE_MULTIPLAYER_FIND_GAME = 30,
+ MENUPAGE_MULTIPLAYER_MODE = 31,
+ MENUPAGE_MULTIPLAYER_CREATE = 32,
+ MENUPAGE_MULTIPLAYER_START = 33,
+ MENUPAGE_SKIN_SELECT_OLD = 34,
+ MENUPAGE_CONTROLLER_PC = 35,
+ MENUPAGE_CONTROLLER_PC_OLD1 = 36,
+ MENUPAGE_CONTROLLER_PC_OLD2 = 37,
+ MENUPAGE_CONTROLLER_PC_OLD3 = 38,
+ MENUPAGE_CONTROLLER_PC_OLD4 = 39,
+ MENUPAGE_CONTROLLER_DEBUG = 40,
+ MENUPAGE_OPTIONS = 41,
+ MENUPAGE_EXIT = 42,
+ MENUPAGE_SAVING_IN_PROGRESS = 43,
+ MENUPAGE_SAVE_SUCCESSFUL = 44,
+ MENUPAGE_DELETING = 45,
+ MENUPAGE_DELETE_SUCCESS = 46,
+ MENUPAGE_SAVE_FAILED = 47,
+ MENUPAGE_LOAD_FAILED = 48,
+ MENUPAGE_LOAD_FAILED_2 = 49,
+ MENUPAGE_FILTER_GAME = 50,
+ MENUPAGE_START_MENU = 51,
+ MENUPAGE_PAUSE_MENU = 52,
+ MENUPAGE_CHOOSE_MODE = 53,
+ MENUPAGE_SKIN_SELECT = 54,
+ MENUPAGE_KEYBOARD_CONTROLS = 55,
+ MENUPAGE_MOUSE_CONTROLS = 56,
+ MENUPAGE_57 = 57,
+ MENUPAGE_58 = 58,
+ MENUPAGES
+};
+
+enum eMenuAction
+{
+ MENUACTION_NOTHING,
+ MENUACTION_LABEL,
+ MENUACTION_CHANGEMENU,
+ MENUACTION_CTRLVIBRATION,
+ MENUACTION_CTRLCONFIG,
+ MENUACTION_CTRLDISPLAY,
+ MENUACTION_FRAMESYNC,
+ MENUACTION_FRAMELIMIT,
+ MENUACTION_TRAILS,
+ MENUACTION_SUBTITLES,
+ MENUACTION_WIDESCREEN,
+ MENUACTION_BRIGHTNESS,
+ MENUACTION_DRAWDIST,
+ MENUACTION_MUSICVOLUME,
+ MENUACTION_SFXVOLUME,
+ MENUACTION_UNK15,
+ MENUACTION_RADIO,
+ MENUACTION_LANG_ENG,
+ MENUACTION_LANG_FRE,
+ MENUACTION_LANG_GER,
+ MENUACTION_LANG_ITA,
+ MENUACTION_LANG_SPA,
+ MENUACTION_UPDATESAVE,
+ MENUACTION_CHECKSAVE,
+ MENUACTION_UNK24,
+ MENUACTION_NEWGAME,
+ MENUACTION_RELOADIDE,
+ MENUACTION_RELOADIPL,
+ MENUACTION_SETDBGFLAG,
+ MENUACTION_SWITCHBIGWHITEDEBUGLIGHT,
+ MENUACTION_PEDROADGROUPS,
+ MENUACTION_CARROADGROUPS,
+ MENUACTION_COLLISIONPOLYS,
+ MENUACTION_REGMEMCARD1,
+ MENUACTION_TESTFORMATMEMCARD1,
+ MENUACTION_TESTUNFORMATMEMCARD1,
+ MENUACTION_CREATEROOTDIR,
+ MENUACTION_CREATELOADICONS,
+ MENUACTION_FILLWITHGUFF,
+ MENUACTION_SAVEONLYTHEGAME,
+ MENUACTION_SAVEGAME,
+ MENUACTION_SAVEGAMEUNDERGTA,
+ MENUACTION_CREATECOPYPROTECTED,
+ MENUACTION_TESTSAVE,
+ MENUACTION_TESTLOAD,
+ MENUACTION_TESTDELETE,
+ MENUACTION_PARSEHEAP,
+ MENUACTION_SHOWCULL,
+ MENUACTION_MEMCARDSAVECONFIRM,
+ MENUACTION_UPDATEMEMCARDSAVE,
+ MENUACTION_UNK50,
+ MENUACTION_DEBUGSTREAM,
+ MENUACTION_MPMAP_LIBERTY,
+ MENUACTION_MPMAP_REDLIGHT,
+ MENUACTION_MPMAP_CHINATOWN,
+ MENUACTION_MPMAP_TOWER,
+ MENUACTION_MPMAP_SEWER,
+ MENUACTION_MPMAP_INDUSTPARK,
+ MENUACTION_MPMAP_DOCKS,
+ MENUACTION_MPMAP_STAUNTON,
+ MENUACTION_MPMAP_DEATHMATCH1,
+ MENUACTION_MPMAP_DEATHMATCH2,
+ MENUACTION_MPMAP_TEAMDEATH1,
+ MENUACTION_MPMAP_TEAMDEATH2,
+ MENUACTION_MPMAP_STASH,
+ MENUACTION_MPMAP_CAPTURE,
+ MENUACTION_MPMAP_RATRACE,
+ MENUACTION_MPMAP_DOMINATION,
+ MENUACTION_STARTMP,
+ MENUACTION_UNK69,
+ MENUACTION_UNK70,
+ MENUACTION_FINDMP,
+ MENUACTION_REDEFCTRL,
+ MENUACTION_UNK73,
+ MENUACTION_INITMP,
+ MENUACTION_MP_PLAYERCOLOR,
+ MENUACTION_MP_PLAYERNAME,
+ MENUACTION_MP_GAMENAME,
+ MENUACTION_GETKEY,
+ MENUACTION_SHOWHEADBOB,
+ MENUACTION_UNK80,
+ MENUACTION_INVVERT,
+ MENUACTION_CANCLEGAME,
+ MENUACTION_MP_PLAYERNUMBER,
+ MENUACTION_MOUSESENS,
+ MENUACTION_CHECKMPGAMES,
+ MENUACTION_CHECKMPPING,
+ MENUACTION_MP_SERVER,
+ MENUACTION_MP_MAP,
+ MENUACTION_MP_GAMETYPE,
+ MENUACTION_MP_LAN,
+ MENUACTION_MP_INTERNET,
+ MENUACTION_RESUME,
+ MENUACTION_DONTCANCLE,
+ MENUACTION_SCREENRES,
+ MENUACTION_AUDIOHW,
+ MENUACTION_SPEAKERCONF,
+ MENUACTION_PLAYERSETUP,
+ MENUACTION_RESTOREDEF,
+ MENUACTION_CTRLMETHOD,
+ MENUACTION_DYNAMICACOUSTIC,
+ MENUACTION_LOADRADIO,
+ MENUACTION_MOUSESTEER,
+ MENUACTION_UNK103,
+ MENUACTION_UNK104,
+ MENUACTION_UNK105,
+ MENUACTION_UNK106,
+ MENUACTION_UNK107,
+ MENUACTION_UNK108,
+ MENUACTION_UNK109,
+ MENUACTION_UNK110,
+};
+
+enum eCheckHover
+{
+ ACTIVATE_OPTION = 2,
+ IGNORE_OPTION = 42,
+};
+
+enum eMenuColumns
+{
+ MENUCOLUMN_LEFT,
+ MENUCOLUMN_CENTER,
+ MENUCOLUMN_RIGHT,
+ MENUCOLUMNS,
+};
+
+enum eMenuRow
+{
+ MENUROW_NONE = -1,
+ MENUROW_0,
+ MENUROW_1,
+ MENUROW_2,
+ MENUROW_3,
+ MENUROW_4,
+ MENUROW_5,
+ MENUROW_6,
+ MENUROW_7,
+ MENUROW_8,
+ MENUROW_9,
+ MENUROW_10,
+ MENUROW_11,
+ MENUROW_12,
+ MENUROW_13,
+ MENUROW_14,
+ MENUROW_15,
+ MENUROW_16,
+ MENUROW_17,
+ MENUROWS,
+};
+
+struct tSkinInfo
+{
+ int field_0;
+ char skinName[256];
+ char currSkinName[256];
+ char date[256];
+ int field_304;
+};
+
+struct CMenuScreen
+{
+ char m_ScreenName[8];
+ int32 unk;
+ int32 m_PreviousPage[2]; // eMenuScreen
+ int32 m_ParentEntry[2]; // eMenuRow
+
+ struct CMenuEntry
+ {
+ int32 m_Action; // eMenuAction
+ char m_EntryName[8];
+ int32 m_SaveSlot; // eSaveSlot
+ int32 m_TargetMenu; // eMenuScreen
+ } m_aEntries[MENUROWS];
+};
+
+class CMenuManager
+{
+public:
+ int32 m_nPrefsVideoMode;
+ int32 m_nDisplayVideoMode;
+ int8 m_nPrefsAudio3DProviderIndex;
+ bool m_bKeyChangeNotProcessed;
+ char m_aSkinName[256];
+ int32 m_nHelperTextMsgId;
+ bool m_bLanguageLoaded;
+ bool m_bMenuActive;
+ char field_112;
+ char field_113;
+ bool m_bStartGameLoading;
+ bool m_bFirstTime;
+ bool m_bGameNotLoaded;
+ int32 m_nMousePosX;
+ int32 m_nMousePosY;
+ int32 m_nMouseTempPosX;
+ int32 m_nMouseTempPosY;
+ bool m_bShowMouse;
+ tSkinInfo field_12C;
+ tSkinInfo *m_pSelectedSkin;
+ tSkinInfo *field_438;
+ float field_43C;
+ int field_440;
+ int m_nSkinsTotal;
+ char _unk0[4];
+ int field_44C;
+ bool m_bSkinsFound;
+ bool m_bQuitGameNoCD;
+ char field_452;
+ bool m_bSaveMenuActive;
+ bool m_bLoadingSavedGame;
+ char field_455;
+ char field_456;
+ bool m_bSpritesLoaded;
+ CSprite2d m_aFrontEndSprites[28];
+ CSprite2d m_aMenuSprites[20];
+ int field_518;
+ int m_nMenuFadeAlpha;
+ char field_520;
+ char field_521;
+ char field_522;
+ char field_523;
+ char field_524;
+ int m_CurrCntrlAction;
+ char _unk1[4];
+ int field_530;
+ char field_534;
+ char field_535;
+ int8 field_536;
+ int m_nHelperTextAlpha;
+ int m_nMouseOldPosX;
+ int m_nMouseOldPosY;
+ int m_nHoverOption;
+ int m_nCurrScreen;
+ int m_nCurrOption;
+ int m_nPrevOption;
+ int m_nPrevScreen;
+ int field_558;
+ int m_nCurrSaveSlot;
+ int m_nScreenChangeDelayTimer;
+
+ static int32 &OS_Language;
+ static int8 &m_PrefsUseVibration;
+ static int8 &m_DisplayControllerOnFoot;
+ static int8 &m_PrefsUseWideScreen;
+ static int8 &m_PrefsRadioStation;
+ static int8 &m_PrefsVsync;
+ static int8 &m_PrefsVsyncDisp;
+ static int8 &m_PrefsFrameLimiter;
+ static int8 &m_PrefsShowSubtitles;
+ static int8 &m_PrefsSpeakers;
+ static int8 &m_ControlMethod;
+ static int8 &m_PrefsDMA;
+ static int8 &m_PrefsLanguage;
+ static int8 &m_bDisableMouseSteering;
+ static int32 &m_PrefsBrightness;
+ static float &m_PrefsLOD;
+ static int8 &m_bFrontEnd_ReloadObrTxtGxt;
+ static int32 &m_PrefsMusicVolume;
+ static int32 &m_PrefsSfxVolume;
+ static uint8 *m_PrefsSkinFile;
+
+ static bool &m_bStartUpFrontEndRequested;
+ static bool &m_bShutDownFrontEndRequested;
+ static bool &m_PrefsAllowNastyGame;
+
+public:
+ void BuildStatLine(char *, void *, uint16, void *);
+ static void CentreMousePointer();
+ void CheckCodesForControls(int, int);
+ bool CheckHover(int x1, int x2, int y1, int y2);
+ void CheckSliderMovement(int);
+ int CostructStatLine(int);
+ void DisplayHelperText();
+ float DisplaySlider(float, float, float, float, float, float);
+ void DoSettingsBeforeStartingAGame();
+ void Draw();
+ void DrawControllerBound(int, int, int, uint8);
+ void DrawControllerScreenExtraText(int, int, int);
+ void DrawControllerSetupScreen();
+ void DrawFrontEnd();
+ void DrawFrontEndNormal();
+ void DrawPlayerSetupScreen();
+ int FadeIn(int alpha);
+ void FilterOutColorMarkersFromString(uint16, CRGBA &);
+ int GetStartOptionsCntrlConfigScreens();
+ static void InitialiseChangedLanguageSettings();
+ void LoadAllTextures();
+ void LoadSettings();
+ static void MessageScreen(char *);
+ static void PickNewPlayerColour();
+ void PrintBriefs();
+ static void PrintErrorMessage();
+ void PrintStats();
+ void Process();
+ void ProcessButtonPresses();
+ void ProcessOnOffMenuOptions();
+ static void RequestFrontEndShutdown();
+ static void RequestFrontEndStartUp();
+ void ResetHelperText();
+ void SaveLoadFileError_SetUpErrorScreen();
+ void SaveSettings();
+ void SetHelperText(int text);
+ void ShutdownJustMenu();
+ static float StretchX(float);
+ static float StretchY(float);
+ void SwitchMenuOnAndOff();
+ void UnloadTextures();
+ void WaitForUserCD();
+
+ // New content:
+ uint8 GetNumberOfMenuOptions();
+ void SwitchToNewScreen(int8 screen);
+ void SetDefaultPreferences(int8 screen);
+
+};
+
+static_assert(sizeof(CMenuManager) == 0x564, "CMenuManager: error");
+
+extern CMenuManager &FrontEndMenuManager;
diff --git a/src/core/Game.cpp b/src/core/Game.cpp
new file mode 100644
index 00000000..cbd55c48
--- /dev/null
+++ b/src/core/Game.cpp
@@ -0,0 +1,23 @@
+#include "common.h"
+#include "patcher.h"
+#include "Game.h"
+
+eLevelName &CGame::currLevel = *(eLevelName*)0x941514;
+bool &CGame::bDemoMode = *(bool*)0x5F4DD0;
+bool &CGame::nastyGame = *(bool*)0x5F4DD4;
+bool &CGame::frenchGame = *(bool*)0x95CDCB;
+bool &CGame::germanGame = *(bool*)0x95CD1E;
+bool &CGame::noProstitutes = *(bool*)0x95CDCF;
+bool &CGame::playingIntro = *(bool*)0x95CDC2;
+char *CGame::aDatFile = (char*)0x773A48;
+
+WRAPPER void CGame::Initialise(const char *datFile) { EAXJMP(0x48BED0); }
+WRAPPER void CGame::Process(void) { EAXJMP(0x48C850); }
+WRAPPER bool CGame::InitialiseOnceBeforeRW(void) { EAXJMP(0x48BB80); }
+WRAPPER bool CGame::InitialiseRenderWare(void) { EAXJMP(0x48BBA0); }
+WRAPPER void CGame::ShutdownRenderWare(void) { EAXJMP(0x48BCB0); }
+WRAPPER void CGame::FinalShutdown(void) { EAXJMP(0x48BEC0); }
+WRAPPER void CGame::ShutDown(void) { EAXJMP(0x48C3A0); }
+WRAPPER void CGame::ShutDownForRestart(void) { EAXJMP(0x48C6B0); }
+WRAPPER void CGame::InitialiseWhenRestarting(void) { EAXJMP(0x48C740); }
+WRAPPER bool CGame::InitialiseOnceAfterRW(void) { EAXJMP(0x48BD50); }
diff --git a/src/core/Game.h b/src/core/Game.h
new file mode 100644
index 00000000..3bc3e633
--- /dev/null
+++ b/src/core/Game.h
@@ -0,0 +1,37 @@
+#pragma once
+
+enum eLevelName
+{
+ LEVEL_NONE = 0,
+ LEVEL_INDUSTRIAL,
+ LEVEL_COMMERCIAL,
+ LEVEL_SUBURBAN
+};
+
+class CGame
+{
+public:
+ static eLevelName &currLevel;
+ static bool &bDemoMode;
+ static bool &nastyGame;
+ static bool &frenchGame;
+ static bool &germanGame;
+ static bool &noProstitutes;
+ static bool &playingIntro;
+ static char *aDatFile; //[32];
+
+ static void Initialise(const char *datFile);
+ static bool InitialiseOnceBeforeRW(void);
+ static bool InitialiseRenderWare(void);
+ static bool InitialiseOnceAfterRW(void);
+ static void InitialiseWhenRestarting(void);
+ static void ShutDown(void);
+ static void ShutdownRenderWare(void);
+ static void FinalShutdown(void);
+ static void ShutDownForRestart(void);
+ static void Process(void);
+
+ // NB: these do something on PS2
+ static void TidyUpMemory(bool, bool) {}
+ static void DrasticTidyUpMemory(void) {}
+};
diff --git a/src/core/General.h b/src/core/General.h
new file mode 100644
index 00000000..cae1caa0
--- /dev/null
+++ b/src/core/General.h
@@ -0,0 +1,91 @@
+#pragma once
+
+class CGeneral
+{
+public:
+ static float GetATanOfXY(float x, float y){
+ if(x == 0.0f && y == 0.0f)
+ return 0.0f;
+ float xabs = fabs(x);
+ float yabs = fabs(y);
+
+ if(xabs < yabs){
+ if(y > 0.0f){
+ if(x > 0.0f)
+ return 0.5f*PI - atan2(x / y, 1.0f);
+ else
+ return 0.5f*PI + atan2(-x / y, 1.0f);
+ }else{
+ if(x > 0.0f)
+ return 1.5f*PI + atan2(x / -y, 1.0f);
+ else
+ return 1.5f*PI - atan2(-x / -y, 1.0f);
+ }
+ }else{
+ if(y > 0.0f){
+ if(x > 0.0f)
+ return atan2(y / x, 1.0f);
+ else
+ return PI - atan2(y / -x, 1.0f);
+ }else{
+ if(x > 0.0f)
+ return 2.0f*PI - atan2(-y / x, 1.0f);
+ else
+ return PI + atan2(-y / -x, 1.0f);
+ }
+ }
+ }
+
+ static float LimitRadianAngle(float angle)
+ {
+ float result;
+
+ if (angle < -25.0f)
+ result = -25.0f;
+ else if (angle > 25.0f)
+ result = 25.0f;
+ else
+ result = angle;
+
+ while (result >= PI) {
+ result -= 2 * PI;
+ }
+
+ while (result < -PI) {
+ result += 2 * PI;
+ }
+
+ return result;
+ }
+
+ static float GetRadianAngleBetweenPoints(float x1, float y1, float x2, float y2)
+ {
+ float x = x2 - x1;
+ float y = y2 - y1;
+
+ if (y == 0.0f)
+ y = 0.0001f;
+
+ if (x > 0.0f) {
+ if (y > 0.0f)
+ return PI - atan2(x / y, 1.0f);
+ else
+ return -atan2(x / y, 1.0f);
+ } else {
+ if (y > 0.0f)
+ return -(PI + atan2(x / y, 1.0f));
+ else
+ return -atan2(x / y, 1.0f);
+ }
+ }
+
+ // not too sure about all these...
+ static uint16 GetRandomNumber(void)
+ { return myrand() & MYRAND_MAX; }
+ // Probably don't want to ever reach high
+ static float GetRandomNumberInRange(float low, float high)
+ { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); }
+
+ static int32 GetRandomNumberInRange(int32 low, int32 high)
+ { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); }
+};
diff --git a/src/core/Lists.cpp b/src/core/Lists.cpp
new file mode 100644
index 00000000..448a0ff1
--- /dev/null
+++ b/src/core/Lists.cpp
@@ -0,0 +1,26 @@
+#include "common.h"
+#include "Pools.h"
+#include "Lists.h"
+
+void*
+CPtrNode::operator new(size_t){
+ CPtrNode *node = CPools::GetPtrNodePool()->New();
+ assert(node);
+ return node;
+}
+
+void
+CPtrNode::operator delete(void *p, size_t){
+ CPools::GetPtrNodePool()->Delete((CPtrNode*)p);
+}
+
+void*
+CEntryInfoNode::operator new(size_t){
+ CEntryInfoNode *node = CPools::GetEntryInfoNodePool()->New();
+ assert(node);
+ return node;
+}
+void
+CEntryInfoNode::operator delete(void *p, size_t){
+ CPools::GetEntryInfoNodePool()->Delete((CEntryInfoNode*)p);
+}
diff --git a/src/core/Lists.h b/src/core/Lists.h
new file mode 100644
index 00000000..7572e882
--- /dev/null
+++ b/src/core/Lists.h
@@ -0,0 +1,130 @@
+#pragma once
+
+class CPtrNode
+{
+public:
+ void *item;
+ CPtrNode *prev;
+ CPtrNode *next;
+
+ void *operator new(size_t);
+ void operator delete(void *p, size_t);
+};
+
+class CPtrList
+{
+public:
+ CPtrNode *first;
+
+ CPtrList(void) { first = nil; }
+ ~CPtrList(void) { Flush(); }
+ CPtrNode *FindItem(void *item){
+ CPtrNode *node;
+ for(node = first; node; node = node->next)
+ if(node->item == item)
+ return node;
+ return nil;
+ }
+ CPtrNode *InsertNode(CPtrNode *node){
+ node->prev = nil;
+ node->next = first;
+ if(first)
+ first->prev = node;
+ first = node;
+ return node;
+ }
+ CPtrNode *InsertItem(void *item){
+ CPtrNode *node = new CPtrNode;
+ node->item = item;
+ InsertNode(node);
+ return node;
+ }
+ void RemoveNode(CPtrNode *node){
+ if(node == first)
+ first = node->next;
+ if(node->prev)
+ node->prev->next = node->next;
+ if(node->next)
+ node->next->prev = node->prev;
+ }
+ void DeleteNode(CPtrNode *node){
+ RemoveNode(node);
+ delete node;
+ }
+ void RemoveItem(void *item){
+ CPtrNode *node, *next;
+ for(node = first; node; node = next){
+ next = node->next;
+ if(node->item == item)
+ DeleteNode(node);
+ }
+ }
+ void Flush(void){
+ CPtrNode *node, *next;
+ for(node = first; node; node = next){
+ next = node->next;
+ DeleteNode(node);
+ }
+ }
+};
+
+class CSector;
+
+// This records in which sector list a Physical is
+class CEntryInfoNode
+{
+public:
+ CPtrList *list; // list in sector
+ CPtrNode *listnode; // node in list
+ CSector *sector;
+
+ CEntryInfoNode *prev;
+ CEntryInfoNode *next;
+
+ void *operator new(size_t);
+ void operator delete(void *p, size_t);
+};
+
+class CEntryInfoList
+{
+public:
+ CEntryInfoNode *first;
+
+ CEntryInfoList(void) { first = nil; }
+ ~CEntryInfoList(void) { Flush(); }
+ CEntryInfoNode *InsertNode(CEntryInfoNode *node){
+ node->prev = nil;
+ node->next = first;
+ if(first)
+ first->prev = node;
+ first = node;
+ return node;
+ }
+ CEntryInfoNode *InsertItem(CPtrList *list, CPtrNode *listnode, CSector *sect){
+ CEntryInfoNode *node = new CEntryInfoNode;
+ node->list = list;
+ node->listnode = listnode;
+ node->sector = sect;
+ InsertNode(node);
+ return node;
+ }
+ void RemoveNode(CEntryInfoNode *node){
+ if(node == first)
+ first = node->next;
+ if(node->prev)
+ node->prev->next = node->next;
+ if(node->next)
+ node->next->prev = node->prev;
+ }
+ void DeleteNode(CEntryInfoNode *node){
+ RemoveNode(node);
+ delete node;
+ }
+ void Flush(void){
+ CEntryInfoNode *node, *next;
+ for(node = first; node; node = next){
+ next = node->next;
+ DeleteNode(node);
+ }
+ }
+};
diff --git a/src/core/MenuScreens.h b/src/core/MenuScreens.h
new file mode 100644
index 00000000..2da81f1d
--- /dev/null
+++ b/src/core/MenuScreens.h
@@ -0,0 +1,380 @@
+#pragma once
+
+const CMenuScreen aScreens[] = {
+ // MENUPAGE_NONE = 0
+ { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, },
+
+ // MENUPAGE_STATS = 1
+ { "FET_STA", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_5, MENUROW_2,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_NEW_GAME = 2
+ { "FET_SGA", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_0, MENUROW_1,
+ MENUACTION_CHANGEMENU, "FES_SNG", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD,
+ MENUACTION_CHANGEMENU, "GMLOAD", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT,
+ MENUACTION_CHANGEMENU, "FES_DGA", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_BRIEFS = 3
+ { "FET_BRE", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_6, MENUROW_3,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENU_CONTROLLER_SETTINGS = 4
+ { "FET_CON", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_SOUND_SETTINGS = 5
+ { "FET_AUD", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_1, MENUROW_1,
+ MENUACTION_MUSICVOLUME, "FEA_MUS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_SFXVOLUME, "FEA_SFX", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_AUDIOHW, "FEA_3DH", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_SPEAKERCONF, "FEA_SPK", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_DYNAMICACOUSTIC, "FET_DAM", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_RADIO, "FEA_RSS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ },
+
+ // MENUPAGE_GRAPHICS_SETTINGS = 6
+ { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_2, MENUROW_2,
+ MENUACTION_BRIGHTNESS, "FED_BRI", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_DRAWDIST, "FEM_LOD", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ //MENUACTION_FRAMESYNC, "FEM_VSC", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_FRAMELIMIT, "FEM_FRM", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_TRAILS, "FED_TRA", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_SUBTITLES, "FED_SUB", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_WIDESCREEN, "FED_WIS", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_SCREENRES, "FED_RES", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_LANGUAGE_SETTINGS = 7
+ { "FET_LAN", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_3, MENUROW_3,
+ MENUACTION_LANG_ENG, "FEL_ENG", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_LANG_FRE, "FEL_FRE", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_LANG_GER, "FEL_GER", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_LANG_ITA, "FEL_ITA", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_LANG_SPA, "FEL_SPA", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_CHOOSE_LOAD_SLOT = 8
+ { "FET_LG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_1, MENUROW_1,
+ MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME,
+ MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM,
+ },
+
+ // MENUPAGE_CHOOSE_DELETE_SLOT = 9
+ { "FET_DG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_2, MENUROW_2,
+ MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME,
+ MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM,
+ MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM,
+ },
+
+ // MENUPAGE_NEW_GAME_RELOAD = 10
+ { "FET_NG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FESZ_QR", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NEW_GAME,
+ MENUACTION_NEWGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_LOAD_SLOT_CONFIRM = 11
+ { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FESZ_QL", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT,
+ MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS,
+ },
+
+ // MENUPAGE_DELETE_SLOT_CONFIRM = 12
+ { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FESZ_QD", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT,
+ MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_DELETING,
+ },
+
+ // MENUPAGE_13 = 13
+ { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_LOADING_IN_PROGRESS = 14
+ { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FED_LDW", SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM,
+ },
+
+ // MENUPAGE_DELETING_IN_PROGRESS = 15
+ { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FEDL_WR", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_16 = 16
+ { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FES_LOE", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_DELETE_FAILED = 17
+ { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FES_DEE", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT,
+ },
+
+ // MENUPAGE_DEBUG_MENU = 18
+ { "FED_DBG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MEMORY_CARD_1 = 19
+ { "FEM_MCM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MEMORY_CARD_2 = 20
+ { "FEM_MC2", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_MAIN = 21
+ { "FET_MP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_SAVE_FAILED_1 = 22
+ { "MCDNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_SAVE_FAILED_2 = 23
+ { "MCGNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_SAVE = 24
+ { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FES_SCG", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_UPDATESAVE, "GMSAVE", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT,
+ MENUACTION_UPDATEMEMCARDSAVE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_NO_MEMORY_CARD = 25
+ { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CHOOSE_SAVE_SLOT = 26
+ { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_UPDATEMEMCARDSAVE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_UPDATESAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ MENUACTION_UPDATESAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM,
+ },
+
+ // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27
+ { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FESZ_QO", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS,
+ MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT,
+ },
+
+ // MENUPAGE_MULTIPLAYER_MAP = 28
+ { "FET_MAP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_CONNECTION = 29
+ { "FET_CON", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_FIND_GAME = 30
+ { "FET_FG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_MODE = 31
+ { "FET_GT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_CREATE = 32
+ { "FET_HG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_MULTIPLAYER_START = 33
+ { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_SKIN_SELECT_OLD = 34
+ { "FET_PS", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CONTROLLER_PC = 35
+ { "FET_CTL", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_0, MENUROW_0,
+ MENUACTION_CTRLMETHOD, "FET_CME", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC,
+ MENUACTION_CHANGEMENU, "FET_RDK", SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS,
+ MENUACTION_CHANGEMENU, "FET_AMS", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS,
+ MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_CONTROLLER_PC_OLD1 = 36
+ { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CONTROLLER_PC_OLD2 = 37
+ { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CONTROLLER_PC_OLD3 = 38
+ { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CONTROLLER_PC_OLD4 = 39
+ { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_CONTROLLER_DEBUG = 40
+ { "FEC_DBG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_OPTIONS = 41
+ { "FET_OPT", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_1, MENUROW_4,
+ MENUACTION_CHANGEMENU, "FET_CTL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC,
+ MENUACTION_CHANGEMENU, "FET_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS,
+ MENUACTION_CHANGEMENU, "FET_DIS", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS,
+ MENUACTION_CHANGEMENU, "FET_LAN", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS,
+ //MENUACTION_CHANGEMENU, "FET_PSU", SAVESLOT_NONE, MENUPAGE_SKIN_SELECT,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_EXIT = 42
+ { "FET_QG", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_2, MENUROW_5,
+ MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CANCLEGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_SAVING_IN_PROGRESS = 43
+ { "", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FES_WAR", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_SAVE_SUCCESSFUL = 44
+ { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FES_SSC", SAVESLOT_LABEL, MENUPAGE_NONE,
+ MENUACTION_UPDATEMEMCARDSAVE, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT,
+ },
+
+ // MENUPAGE_DELETING = 45
+ { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FED_DLW", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_DELETE_SUCCESS = 46
+ { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "DEL_FNM", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT,
+ },
+
+ // MENUPAGE_SAVE_FAILED = 47
+ { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT,
+ },
+
+ // MENUPAGE_LOAD_FAILED = 48
+ { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_LOAD_FAILED_2 = 49
+ { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0,
+ MENUACTION_LABEL, "FEC_LUN", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT,
+ },
+
+ // MENUPAGE_FILTER_GAME = 50
+ { "FIL_FLT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_START_MENU = 51
+ { "FEM_MM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME,
+ MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS,
+ MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT,
+ },
+
+ // MENUPAGE_PAUSE_MENU = 52
+ { "FET_PAU", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+ MENUACTION_RESUME, "FEM_RES", SAVESLOT_NONE, MENUPAGE_NONE,
+ MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME,
+ MENUACTION_CHANGEMENU, "FEP_STA", SAVESLOT_NONE, MENUPAGE_STATS,
+ MENUACTION_CHANGEMENU, "FEP_BRI", SAVESLOT_NONE, MENUPAGE_BRIEFS,
+ MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS,
+ MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT,
+ },
+
+ // MENUPAGE_CHOOSE_MODE = 53
+ { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_SKIN_SELECT = 54
+ { "FET_PSU", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_4, MENUROW_4,
+ //MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN,
+ },
+
+ // MENUPAGE_KEYBOARD_CONTROLS = 55
+ { "FET_STI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_1, MENUROW_1,
+ //MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC,
+ },
+
+ // MENUPAGE_MOUSE_CONTROLS = 56
+ { "FET_MTI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_2, MENUROW_2,
+ MENUACTION_MOUSESENS, "FEC_MSH", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS,
+ MENUACTION_INVVERT, "FEC_IVV", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS,
+ MENUACTION_MOUSESTEER, "FET_MST", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS,
+ MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE,
+ },
+
+ // MENUPAGE_57 = 57
+ { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+
+ // MENUPAGE_58 = 58
+ { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0,
+
+ },
+};
diff --git a/src/core/Messages.cpp b/src/core/Messages.cpp
new file mode 100644
index 00000000..7fc23593
--- /dev/null
+++ b/src/core/Messages.cpp
@@ -0,0 +1,15 @@
+#include "common.h"
+#include "patcher.h"
+#include "Messages.h"
+
+WRAPPER void CMessages::Display(void) { EAXJMP(0x529800); }
+WRAPPER void CMessages::ClearAllMessagesDisplayedByGame(void) { EAXJMP(0x52B670); }
+WRAPPER int CMessages::WideStringCopy(wchar* dst, wchar* src, unsigned short size) { EAXJMP(0x5294B0); }
+WRAPPER char CMessages::WideStringCompare(wchar* str1, wchar* str2, unsigned short size) { EAXJMP(0x529510); }
+WRAPPER void CMessages::InsertNumberInString(wchar* src, int n1, int n2, int n3, int n4, int n5, int n6, wchar* dst) { EAXJMP(0x52A1A0); }
+WRAPPER void CMessages::InsertPlayerControlKeysInString(wchar* src) { EAXJMP(0x52A490); }
+WRAPPER int CMessages::GetWideStringLength(wchar* src) { EAXJMP(0x529490); }
+
+tPreviousBrief *CMessages::PreviousBriefs = (tPreviousBrief *)0x713C08;
+tMessage *CMessages::BriefMessages = (tMessage *)0x8786E0;
+tBigMessage *CMessages::BIGMessages = (tBigMessage *)0x773628;
diff --git a/src/core/Messages.h b/src/core/Messages.h
new file mode 100644
index 00000000..69cf117c
--- /dev/null
+++ b/src/core/Messages.h
@@ -0,0 +1,44 @@
+#pragma once
+
+struct tMessage
+{
+ wchar *m_pText;
+ uint16 m_nFlag;
+private:
+ int8 _pad6[2];
+public:
+ uint32 m_nTime;
+ uint32 m_nStartTime;
+ int32 m_nNumber[6];
+ wchar *m_pString;
+};
+
+struct tBigMessage
+{
+ tMessage m_Current;
+ tMessage m_Stack[3];
+};
+
+struct tPreviousBrief
+{
+ wchar *m_pText;
+ int32 m_nNumber[6];
+ wchar *m_pString;
+};
+
+class CMessages
+{
+public:
+ static tPreviousBrief *PreviousBriefs;
+ static tMessage *BriefMessages;
+ static tBigMessage *BIGMessages;
+
+public:
+ static void Display(void);
+ static void ClearAllMessagesDisplayedByGame(void);
+ static int WideStringCopy(wchar* dst, wchar* src, unsigned short size);
+ static char WideStringCompare(wchar* str1, wchar* str2, unsigned short size);
+ static void InsertNumberInString(wchar* src, int n1, int n2, int n3, int n4, int n5, int n6, wchar* dst);
+ static void InsertPlayerControlKeysInString(wchar* src);
+ static int GetWideStringLength(wchar *src);
+};
diff --git a/src/core/NodeName.cpp b/src/core/NodeName.cpp
new file mode 100644
index 00000000..2aea3c83
--- /dev/null
+++ b/src/core/NodeName.cpp
@@ -0,0 +1,77 @@
+#include "common.h"
+#include "patcher.h"
+#include "NodeName.h"
+
+static int32 &gPluginOffset = *(int32*)0x64C610;
+
+enum
+{
+ ID_NODENAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFE),
+};
+
+#define NODENAMEEXT(o) (RWPLUGINOFFSET(char, o, gPluginOffset))
+
+void*
+NodeNameConstructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ if(gPluginOffset > 0)
+ NODENAMEEXT(object)[0] = '\0';
+ return object;
+}
+
+void*
+NodeNameDestructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ return object;
+}
+
+void*
+NodeNameCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ strncpy(NODENAMEEXT(dstObject), NODENAMEEXT(srcObject), 23);
+ return nil;
+}
+
+RwStream*
+NodeNameStreamRead(RwStream *stream, RwInt32 binaryLength, void *object, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ RwStreamRead(stream, NODENAMEEXT(object), binaryLength);
+ NODENAMEEXT(object)[binaryLength] = '\0';
+ return stream;
+}
+
+RwStream*
+NodeNameStreamWrite(RwStream *stream, RwInt32 binaryLength, const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ RwStreamWrite(stream, NODENAMEEXT(object), binaryLength);
+ return stream;
+}
+
+RwInt32
+NodeNameStreamGetSize(const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject)
+{
+ // game checks for null pointer on node name extension but that really happen
+ return rwstrlen(NODENAMEEXT(object));
+}
+
+bool
+NodeNamePluginAttach(void)
+{
+ gPluginOffset = RwFrameRegisterPlugin(24, ID_NODENAME,
+ NodeNameConstructor,
+ NodeNameDestructor,
+ NodeNameCopy);
+ RwFrameRegisterPluginStream(ID_NODENAME,
+ NodeNameStreamRead,
+ NodeNameStreamWrite,
+ NodeNameStreamGetSize);
+ return gPluginOffset != -1;
+}
+
+char*
+GetFrameNodeName(RwFrame *frame)
+{
+ if(gPluginOffset < 0)
+ return nil;
+ return NODENAMEEXT(frame);
+}
diff --git a/src/core/NodeName.h b/src/core/NodeName.h
new file mode 100644
index 00000000..1a3e057b
--- /dev/null
+++ b/src/core/NodeName.h
@@ -0,0 +1,4 @@
+#pragma once
+
+bool NodeNamePluginAttach(void);
+char *GetFrameNodeName(RwFrame *frame);
diff --git a/src/core/PCSave.cpp b/src/core/PCSave.cpp
new file mode 100644
index 00000000..628e1218
--- /dev/null
+++ b/src/core/PCSave.cpp
@@ -0,0 +1,20 @@
+#include "common.h"
+#include "patcher.h"
+#include "Frontend.h"
+#include "PCSave.h"
+
+WRAPPER void C_PcSave::SetSaveDirectory(const char *path) { EAXJMP(0x591EA0); }
+WRAPPER int8 C_PcSave::PopulateSlotInfo() { EAXJMP(0x592090); }
+WRAPPER int8 C_PcSave::DeleteSlot(int) { EAXJMP(0x5922F0); }
+WRAPPER int8 C_PcSave::SaveSlot(int) { EAXJMP(0x591EC0); }
+
+WRAPPER int8 CheckSlotDataValid(int) { EAXJMP(0x591A40); }
+
+WRAPPER wchar *GetNameOfSavedGame(int counter) { EAXJMP(0x591B60); }
+WRAPPER wchar *GetSavedGameDateAndTime(int counter) { EAXJMP(0x591B50); }
+
+
+C_PcSave PcSaveHelper = *(C_PcSave*)0x8E2C60;
+int *Slots = (int*)0x728040;
+int *SlotFileName = (int*)0x6F07C8;
+int *SlotSaveDate = (int*)0x72B858;
diff --git a/src/core/PCSave.h b/src/core/PCSave.h
new file mode 100644
index 00000000..696e158a
--- /dev/null
+++ b/src/core/PCSave.h
@@ -0,0 +1,21 @@
+#pragma once
+
+class C_PcSave
+{
+public:
+ int32 m_nHelper;
+
+ static void SetSaveDirectory(const char *path);
+ int8 PopulateSlotInfo();
+ int8 DeleteSlot(int);
+ int8 SaveSlot(int);
+};
+
+extern int8 CheckSlotDataValid(int);
+extern wchar *GetNameOfSavedGame(int counter);
+extern wchar *GetSavedGameDateAndTime(int counter);
+
+extern C_PcSave PcSaveHelper;
+extern int *Slots;
+extern int *SlotFileName;
+extern int *SlotSaveDate;
diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp
new file mode 100644
index 00000000..002e7180
--- /dev/null
+++ b/src/core/Pad.cpp
@@ -0,0 +1,2091 @@
+#pragma warning( push )
+#pragma warning( disable : 4005)
+#define DIRECTINPUT_VERSION 0x0800
+#include <dinput.h>
+#pragma warning( pop )
+
+#include "common.h"
+#include "patcher.h"
+#include "Pad.h"
+#include "ControllerConfig.h"
+#include "Timer.h"
+#include "Frontend.h"
+#include "Camera.h"
+#include "Game.h"
+#include "CutsceneMgr.h"
+#include "Font.h"
+#include "Hud.h"
+#include "Text.h"
+#include "Timer.h"
+#include "World.h"
+#include "Vehicle.h"
+#include "Ped.h"
+#include "Population.h"
+#include "Replay.h"
+#include "Weather.h"
+#include "win.h"
+
+CPad *Pads = (CPad*)0x6F0360; // [2]
+CMousePointerStateHelper &MousePointerStateHelper = *(CMousePointerStateHelper*)0x95CC8C;
+
+bool &CPad::bDisplayNoControllerMessage = *(bool *)0x95CD52;
+bool &CPad::bObsoleteControllerMessage = *(bool *)0x95CDB8;
+bool &CPad::m_bMapPadOneToPadTwo = *(bool *)0x95CD48;
+
+CKeyboardState &CPad::OldKeyState = *(CKeyboardState*)0x6F1E70;
+CKeyboardState &CPad::NewKeyState = *(CKeyboardState*)0x6E60D0;
+CKeyboardState &CPad::TempKeyState = *(CKeyboardState*)0x774DE8;
+
+char CPad::KeyBoardCheatString[18];
+
+CMouseControllerState &CPad::OldMouseControllerState = *(CMouseControllerState*)0x8472A0;
+CMouseControllerState &CPad::NewMouseControllerState = *(CMouseControllerState*)0x8809F0;
+CMouseControllerState &CPad::PCTempMouseControllerState = *(CMouseControllerState*)0x6F1E60;
+
+_TODO("gbFastTime");
+extern bool &gbFastTime;
+
+WRAPPER void WeaponCheat() { EAXJMP(0x490D90); }
+WRAPPER void HealthCheat() { EAXJMP(0x490E70); }
+WRAPPER void TankCheat() { EAXJMP(0x490EE0); }
+WRAPPER void BlowUpCarsCheat() { EAXJMP(0x491040); }
+WRAPPER void ChangePlayerCheat() { EAXJMP(0x4910B0); }
+WRAPPER void MayhemCheat() { EAXJMP(0x4911C0); }
+WRAPPER void EverybodyAttacksPlayerCheat() { EAXJMP(0x491270); }
+WRAPPER void WeaponsForAllCheat() { EAXJMP(0x491370); }
+WRAPPER void FastTimeCheat() { EAXJMP(0x4913A0); }
+WRAPPER void SlowTimeCheat() { EAXJMP(0x4913F0); }
+WRAPPER void MoneyCheat() { EAXJMP(0x491430); }
+WRAPPER void ArmourCheat() { EAXJMP(0x491460); }
+WRAPPER void WantedLevelUpCheat() { EAXJMP(0x491490); }
+WRAPPER void WantedLevelDownCheat() { EAXJMP(0x4914F0); }
+WRAPPER void SunnyWeatherCheat() { EAXJMP(0x491520); }
+WRAPPER void CloudyWeatherCheat() { EAXJMP(0x491550); }
+WRAPPER void RainyWeatherCheat() { EAXJMP(0x491580); }
+WRAPPER void FoggyWeatherCheat() { EAXJMP(0x4915B0); }
+WRAPPER void FastWeatherCheat() { EAXJMP(0x4915E0); }
+WRAPPER void OnlyRenderWheelsCheat() { EAXJMP(0x491610); }
+WRAPPER void ChittyChittyBangBangCheat() { EAXJMP(0x491640); }
+WRAPPER void StrongGripCheat() { EAXJMP(0x491670); }
+WRAPPER void NastyLimbsCheat() { EAXJMP(0x4916A0); }
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef KANGAROO_CHEAT
+void KangarooCheat()
+{
+ wchar *string;
+ CPed *playerPed = FindPlayerPed();
+ int m_fMass;
+
+ if (playerPed->m_ped_flagI80) {
+ string = TheText.Get("CHEATOF");
+ m_fMass = 70.0f;
+ } else {
+ string = TheText.Get("CHEAT1");
+ m_fMass = 15.0f;
+ }
+ CHud::SetHelpMessage(string, 1);
+ playerPed->m_ped_flagI80 = !playerPed->m_ped_flagI80;
+
+ playerPed->m_fMass = m_fMass;
+ playerPed->m_fAirResistance = 0.4f / m_fMass;
+}
+#endif
+
+void
+CControllerState::Clear(void)
+{
+ LeftStickX = LeftStickY = RightStickX = RightStickY = 0;
+ LeftShoulder1 = LeftShoulder2 = RightShoulder1 = RightShoulder2 = 0;
+ DPadUp = DPadDown = DPadLeft = DPadRight = 0;
+ Start = Select = 0;
+ Square = Triangle = Cross = Circle = 0;
+ LeftShock = RightShock = 0;
+ NetworkTalk = 0;
+}
+
+void CKeyboardState::Clear()
+{
+ for ( int32 i = 0; i < 12; i++ )
+ F[i] = 0;
+
+ for ( int32 i = 0; i < 256; i++ )
+ VK_KEYS[i] = 0;
+
+ ESC = INS = DEL = HOME = END = PGUP = PGDN = 0;
+
+ UP = DOWN = LEFT = RIGHT = 0;
+
+ NUMLOCK = 0;
+
+ DIV = MUL = SUB = ADD = 0;
+
+ DECIMAL = NUM1 = NUM2 = NUM3 = NUM4 = 0;
+
+ NUM5 = NUM6 = NUM7 = NUM8 = 0;
+
+ NUM9 = NUM0 = SCROLLLOCK = PAUSE = 0;
+
+ BACKSP = TAB = CAPSLOCK = EXTENTER = 0;
+
+ LSHIFT = SHIFT = RSHIFT = LCTRL = RCTRL = LALT = RALT = 0;
+
+ LWIN = RWIN = APPS = 0;
+}
+
+void CPad::Clear(bool bResetPlayerControls)
+{
+ NewState.Clear();
+ OldState.Clear();
+
+ PCTempKeyState.Clear();
+ PCTempJoyState.Clear();
+ PCTempMouseState.Clear();
+
+ NewKeyState.Clear();
+ OldKeyState.Clear();
+ TempKeyState.Clear();
+
+ NewMouseControllerState.Clear();
+ OldMouseControllerState.Clear();
+ PCTempMouseControllerState.Clear();
+
+ Phase = 0;
+ ShakeFreq = 0;
+ ShakeDur = 0;
+
+ if ( bResetPlayerControls )
+ DisablePlayerControls = false;
+
+ bApplyBrakes = false;
+
+
+ for ( int32 i = 0; i < _TODOCONST(5); i++ )
+ bHornHistory[i] = false;
+
+ iCurrHornHistory = 0;
+
+ for ( int32 i = 0; i < _TODOCONST(12); i++ )
+ _unk[i] = ' ';
+
+ LastTimeTouched = CTimer::GetTimeInMilliseconds();
+ AverageWeapon = 0;
+ AverageEntries = 0;
+}
+
+void CPad::ClearMouseHistory()
+{
+ PCTempMouseControllerState.Clear();
+ NewMouseControllerState.Clear();
+ OldMouseControllerState.Clear();
+}
+
+CMouseControllerState::CMouseControllerState()
+{
+ LMB = 0;
+ RMB = 0;
+ MMB = 0;
+ WHEELUP = 0;
+ WHEELDN = 0;
+ MXB1 = 0;
+ MXB2 = 0;
+
+ x = 0.0f;
+ y = 0.0f;
+}
+
+void CMouseControllerState::Clear()
+{
+ LMB = 0;
+ RMB = 0;
+ MMB = 0;
+ WHEELUP = 0;
+ WHEELDN = 0;
+ MXB1 = 0;
+ MXB2 = 0;
+}
+
+CMouseControllerState CMousePointerStateHelper::GetMouseSetUp()
+{
+ CMouseControllerState state;
+
+ if ( PSGLOBAL(mouse) == nil )
+ _InputInitialiseMouse();
+
+ if ( PSGLOBAL(mouse) != nil )
+ {
+ DIDEVCAPS devCaps;
+ devCaps.dwSize = sizeof(DIDEVCAPS);
+
+ PSGLOBAL(mouse)->GetCapabilities(&devCaps);
+
+ switch ( devCaps.dwButtons )
+ {
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ state.MMB = true;
+
+ case 2:
+ state.RMB = true;
+
+ case 1:
+ state.LMB = true;
+ }
+
+ if ( devCaps.dwAxes == 3 )
+ {
+ state.WHEELDN = true;
+ state.WHEELUP = true;
+ }
+ }
+
+ return state;
+}
+
+void CPad::UpdateMouse()
+{
+ if ( IsForegroundApp() )
+ {
+ if ( PSGLOBAL(mouse) == nil )
+ _InputInitialiseMouse();
+
+ DIMOUSESTATE2 state;
+
+ if ( PSGLOBAL(mouse) != nil && SUCCEEDED(_InputGetMouseState(&state)) )
+ {
+ int32 signX = 1;
+ int32 signy = 1;
+
+ if ( !FrontEndMenuManager.m_bMenuActive )
+ {
+ if ( MousePointerStateHelper.bInvertVertically )
+ signy = -1;
+ if ( MousePointerStateHelper.bInvertHorizontally )
+ signX = -1;
+ }
+
+ PCTempMouseControllerState.Clear();
+
+ PCTempMouseControllerState.x = (float)(signX * state.lX);
+ PCTempMouseControllerState.y = (float)(signy * state.lY);
+ PCTempMouseControllerState.LMB = state.rgbButtons[0] & 128;
+ PCTempMouseControllerState.RMB = state.rgbButtons[1] & 128;
+ PCTempMouseControllerState.MMB = state.rgbButtons[2] & 128;
+ PCTempMouseControllerState.MXB1 = state.rgbButtons[3] & 128;
+ PCTempMouseControllerState.MXB2 = state.rgbButtons[4] & 128;
+
+ if ( state.lZ > 0 )
+ PCTempMouseControllerState.WHEELUP = 1;
+ else if ( state.lZ < 0 )
+ PCTempMouseControllerState.WHEELDN = 1;
+
+ OldMouseControllerState = NewMouseControllerState;
+ NewMouseControllerState = PCTempMouseControllerState;
+ }
+ }
+}
+
+CControllerState CPad::ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2)
+{
+ static CControllerState ReconState;
+
+ ReconState.Clear();
+
+#define _RECONCILE_BUTTON(button) \
+ { if ( State1.button || State2.button ) ReconState.button = 255; }
+
+#define _RECONCILE_AXIS_POSITIVE(axis) \
+ { if ( State1.axis >= 0 && State2.axis >= 0 ) ReconState.axis = max(State1.axis, State2.axis); }
+
+#define _RECONCILE_AXIS_NEGATIVE(axis) \
+ { if ( State1.axis <= 0 && State2.axis <= 0 ) ReconState.axis = min(State1.axis, State2.axis); }
+
+#define _RECONCILE_AXIS(axis) \
+ { _RECONCILE_AXIS_POSITIVE(axis); _RECONCILE_AXIS_NEGATIVE(axis); }
+
+#define _FIX_AXIS_DIR(axis) \
+ { if ( State1.axis > 0 && State2.axis < 0 || State1.axis < 0 && State2.axis > 0 ) ReconState.axis = 0; }
+
+#define _FIX_RECON_DIR(pos, neg, axis) \
+ { if ( (ReconState.pos || ReconState.axis < 0) && (ReconState.neg || ReconState.axis > 0) ) { ReconState.pos = 0; ReconState.neg = 0; ReconState.axis = 0; } }
+
+ _RECONCILE_BUTTON(LeftShoulder1);
+ _RECONCILE_BUTTON(LeftShoulder2);
+ _RECONCILE_BUTTON(RightShoulder1);
+ _RECONCILE_BUTTON(RightShoulder2);
+ _RECONCILE_BUTTON(Start);
+ _RECONCILE_BUTTON(Select);
+ _RECONCILE_BUTTON(Square);
+ _RECONCILE_BUTTON(Triangle);
+ _RECONCILE_BUTTON(Cross);
+ _RECONCILE_BUTTON(Circle);
+ _RECONCILE_BUTTON(LeftShock);
+ _RECONCILE_BUTTON(RightShock);
+ _RECONCILE_BUTTON(NetworkTalk);
+ _RECONCILE_AXIS(LeftStickX);
+ _RECONCILE_AXIS(LeftStickY);
+ _FIX_AXIS_DIR(LeftStickX);
+ _FIX_AXIS_DIR(LeftStickY);
+ _RECONCILE_AXIS(RightStickX);
+ _RECONCILE_AXIS(RightStickY);
+ _FIX_AXIS_DIR(RightStickX);
+ _FIX_AXIS_DIR(RightStickY);
+ _RECONCILE_BUTTON(DPadUp);
+ _RECONCILE_BUTTON(DPadDown);
+ _RECONCILE_BUTTON(DPadLeft);
+ _RECONCILE_BUTTON(DPadRight);
+ _FIX_RECON_DIR(DPadUp, DPadDown, LeftStickY);
+ _FIX_RECON_DIR(DPadLeft, DPadRight, LeftStickX);
+
+ return ReconState;
+
+#undef _RECONCILE_BUTTON
+#undef _RECONCILE_AXIS_POSITIVE
+#undef _RECONCILE_AXIS_NEGATIVE
+#undef _RECONCILE_AXIS
+#undef _FIX_AXIS_DIR
+#undef _FIX_RECON_DIR
+}
+
+void CPad::StartShake(int16 nDur, uint8 nFreq)
+{
+ if ( !CMenuManager::m_PrefsUseVibration )
+ return;
+
+ if ( CCutsceneMgr::IsRunning() || CGame::playingIntro )
+ return;
+
+ if ( nFreq == 0 )
+ {
+ ShakeDur = 0;
+ ShakeFreq = 0;
+ return;
+ }
+
+ if ( nDur > ShakeDur )
+ {
+ ShakeDur = nDur;
+ ShakeFreq = nFreq;
+ }
+}
+
+void CPad::StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fZ)
+{
+ if ( !CMenuManager::m_PrefsUseVibration )
+ return;
+
+ if ( CCutsceneMgr::IsRunning() || CGame::playingIntro )
+ return;
+
+ float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, fZ) ).Magnitude();
+
+ if ( fDist < 70.0f )
+ {
+ if ( nFreq == 0 )
+ {
+ ShakeDur = 0;
+ ShakeFreq = 0;
+ return;
+ }
+
+ if ( nDur > ShakeDur )
+ {
+ ShakeDur = nDur;
+ ShakeFreq = nFreq;
+ }
+ }
+}
+
+void CPad::StartShake_Train(float fX, float fY)
+{
+ if ( !CMenuManager::m_PrefsUseVibration )
+ return;
+
+ if ( CCutsceneMgr::IsRunning() || CGame::playingIntro )
+ return;
+
+ if (FindPlayerVehicle() != nil && FindPlayerVehicle()->IsTrain() )
+ return;
+
+ float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, 0.0f) ).Magnitude2D();
+
+ if ( fDist < 70.0f )
+ {
+ int32 freq = (int32)((70.0f - fDist) * 70.0f / 70.0f + 30.0f);
+
+ if ( ShakeDur < 100 )
+ {
+ ShakeDur = 100;
+ ShakeFreq = freq;
+ }
+ }
+}
+
+void CPad::AddToPCCheatString(char c)
+{
+ for ( int32 i = ARRAY_SIZE(KeyBoardCheatString); i >= 0; i-- )
+ KeyBoardCheatString[i + 1] = KeyBoardCheatString[i];
+
+ KeyBoardCheatString[0] = c;
+
+ #define _CHEATCMP(str) strncmp(str, KeyBoardCheatString, sizeof(str)-1)
+
+ // "GUNSGUNSGUNS"
+ if ( !_CHEATCMP("SNUGSNUGSNUG") )
+ WeaponCheat();
+
+ // "IFIWEREARICHMAN"
+ if ( !_CHEATCMP("NAMHCIRAEREWIFI") )
+ MoneyCheat();
+
+ // "GESUNDHEIT"
+ if ( !_CHEATCMP("TIEHDNUSEG") )
+ HealthCheat();
+
+ // "MOREPOLICEPLEASE"
+ if ( !_CHEATCMP("ESAELPECILOPEROM") )
+ WantedLevelUpCheat();
+
+ // "NOPOLICEPLEASE"
+ if ( !_CHEATCMP("ESAELPECILOPON") )
+ WantedLevelDownCheat();
+
+ // "GIVEUSATANK"
+ if ( !_CHEATCMP("KNATASUEVIG") )
+ TankCheat();
+
+ // "BANGBANGBANG"
+ if ( !_CHEATCMP("GNABGNABGNAB") )
+ BlowUpCarsCheat();
+
+ // "ILIKEDRESSINGUP"
+ if ( !_CHEATCMP("PUGNISSERDEKILI") )
+ ChangePlayerCheat();
+
+ // "ITSALLGOINGMAAAD"
+ if ( !_CHEATCMP("DAAAMGNIOGLLASTI") )
+ MayhemCheat();
+
+ // "NOBODYLIKESME"
+ if ( !_CHEATCMP("EMSEKILYDOBON") )
+ EverybodyAttacksPlayerCheat();
+
+ // "WEAPONSFORALL"
+ if ( !_CHEATCMP("LLAROFSNOPAEW") )
+ WeaponsForAllCheat();
+
+ // "TIMEFLIESWHENYOU"
+ if ( !_CHEATCMP("UOYNEHWSEILFEMIT") )
+ FastTimeCheat();
+
+ // "BOOOOORING"
+ if ( !_CHEATCMP("GNIROOOOOB") )
+ SlowTimeCheat();
+
+#ifndef GTA3_1_1_PATCH
+ // "TURTOISE"
+ if ( !_CHEATCMP("ESIOTRUT") )
+ ArmourCheat();
+#else
+ // "TORTOISE"
+ if ( !_CHEATCMP("ESIOTROT") )
+ ArmourCheat();
+#endif
+
+ // "SKINCANCERFORME"
+ if ( !_CHEATCMP("EMROFRECNACNIKS") )
+ SunnyWeatherCheat();
+
+ // "ILIKESCOTLAND"
+ if ( !_CHEATCMP("DNALTOCSEKILI") )
+ CloudyWeatherCheat();
+
+ // "ILOVESCOTLAND"
+ if ( !_CHEATCMP("DNALTOCSEVOLI") )
+ RainyWeatherCheat();
+
+ // "PEASOUP"
+ if ( !_CHEATCMP("PUOSAEP") )
+ FoggyWeatherCheat();
+
+ // "MADWEATHER"
+ if ( !_CHEATCMP("REHTAEWDAM") )
+ FastWeatherCheat();
+
+ // "ANICESETOFWHEELS"
+ if ( !_CHEATCMP("SLEEHWFOTESECINA") )
+ OnlyRenderWheelsCheat();
+
+ // "CHITTYCHITTYBB"
+ if ( !_CHEATCMP("BBYTTIHCYTTIHC") )
+ ChittyChittyBangBangCheat();
+
+ // "CORNERSLIKEMAD"
+ if ( !_CHEATCMP("DAMEKILSRENROC") )
+ StrongGripCheat();
+
+ // "NASTYLIMBSCHEAT"
+ if ( !_CHEATCMP("TAEHCSBMILYTSAN") )
+ NastyLimbsCheat();
+
+#ifdef KANGAROO_CHEAT
+ // "KANGAROO"
+ if (!_CHEATCMP("OORAGNAK"))
+ KangarooCheat();
+#endif
+
+ #undef _CHEATCMP
+}
+
+void CPad::UpdatePads(void)
+{
+ bool bUpdate = true;
+
+ GetPad(0)->UpdateMouse();
+ CapturePad(0);
+
+
+ ControlsManager.ClearSimButtonPressCheckers();
+ ControlsManager.AffectPadFromKeyBoard();
+ ControlsManager.AffectPadFromMouse();
+
+ if ( CReplay::IsPlayingBackFromFile() )
+ bUpdate = false;
+
+ if ( bUpdate )
+ {
+ GetPad(0)->Update(0);
+ }
+
+ GetPad(1)->NewState.Clear();
+ GetPad(1)->OldState.Clear();
+
+ OldKeyState = NewKeyState;
+ NewKeyState = TempKeyState;
+}
+
+void CPad::ProcessPCSpecificStuff(void)
+{
+ ;
+}
+
+void CPad::Update(int16 unk)
+{
+ OldState = NewState;
+
+ NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState);
+ NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState);
+
+ PCTempJoyState.Clear();
+ PCTempKeyState.Clear();
+ PCTempMouseState.Clear();
+
+ ProcessPCSpecificStuff();
+
+ if ( ++iCurrHornHistory >= _TODOCONST(5) )
+ iCurrHornHistory = 0;
+
+ bHornHistory[iCurrHornHistory] = GetHorn();
+
+
+ if ( !bDisplayNoControllerMessage )
+ CGame::bDemoMode = false;
+}
+
+void CPad::DoCheats(void)
+{
+ GetPad(0)->DoCheats(0);
+}
+
+void CPad::DoCheats(int16 unk)
+{
+#ifdef PS2
+ if ( GetTriangleJustDown() )
+ AddToCheatString('T');
+
+ if ( GetCircleJustDown() )
+ AddToCheatString('C');
+
+ if ( GetCrossJustDown() )
+ AddToCheatString('X');
+
+ if ( GetSquareJustDown() )
+ AddToCheatString('S');
+
+ if ( GetDPadUpJustDown() )
+ AddToCheatString('U');
+
+ if ( GetDPadDownJustDown() )
+ AddToCheatString('D');
+
+ if ( GetDPadLeftJustDown() )
+ AddToCheatString('L');
+
+ if ( GetDPadRightJustDown() )
+ AddToCheatString('R');
+
+ if ( GetLeftShoulder1JustDown() )
+ AddToCheatString('1');
+
+ if ( GetLeftShoulder2JustDown() )
+ AddToCheatString('2');
+
+ if ( GetRightShoulder1JustDown() )
+ AddToCheatString('3');
+
+ if ( GetRightShoulder2JustDown() )
+ AddToCheatString('4');
+#endif
+}
+
+void CPad::StopPadsShaking(void)
+{
+ GetPad(0)->StopShaking(0);
+}
+
+void CPad::StopShaking(int16 unk)
+{
+ ;
+}
+
+CPad *CPad::GetPad(int32 pad)
+{
+ return &Pads[pad];
+}
+
+int16 CPad::GetSteeringLeftRight(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ int16 axis = NewState.LeftStickX;
+ int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+
+ break;
+ }
+
+ case 1:
+ case 3:
+ {
+ return NewState.LeftStickX;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetSteeringUpDown(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ int16 axis = NewState.LeftStickY;
+ int16 dpad = (NewState.DPadUp - NewState.DPadDown) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+
+ break;
+ }
+
+ case 1:
+ case 3:
+ {
+ return NewState.LeftStickY;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetCarGunUpDown(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return NewState.RightStickY;
+
+ break;
+ }
+
+ case 3:
+ {
+ return (NewState.DPadUp - NewState.DPadDown) / 2;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetCarGunLeftRight(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return NewState.RightStickX;
+
+ break;
+ }
+
+ case 3:
+ {
+ return (NewState.DPadRight - NewState.DPadLeft) / 2;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetPedWalkLeftRight(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ int16 axis = NewState.LeftStickX;
+ int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+
+ break;
+ }
+
+ case 1:
+ case 3:
+ {
+ return NewState.LeftStickX;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+int16 CPad::GetPedWalkUpDown(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ int16 axis = NewState.LeftStickY;
+ int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+
+ break;
+ }
+
+ case 1:
+ case 3:
+ {
+ return NewState.LeftStickY;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetAnalogueUpDown(void)
+{
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ int16 axis = NewState.LeftStickY;
+ int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+
+ break;
+ }
+
+ case 1:
+ case 3:
+ {
+ return NewState.LeftStickY;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+bool CPad::GetLookLeft(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.LeftShoulder2 && !NewState.RightShoulder2);
+}
+
+bool CPad::GetLookRight(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.RightShoulder2 && !NewState.LeftShoulder2);
+}
+
+
+bool CPad::GetLookBehindForCar(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.RightShoulder2 && NewState.LeftShoulder2);
+}
+
+bool CPad::GetLookBehindForPed(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!NewState.RightShock;
+}
+
+bool CPad::GetHorn(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ {
+ return !!NewState.LeftShock;
+
+ break;
+ }
+
+ case 1:
+ {
+ return !!NewState.LeftShoulder1;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.RightShoulder1;
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!NewState.LeftShock;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::HornJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ {
+ return !!(NewState.LeftShock && !OldState.LeftShock);
+
+ break;
+ }
+
+ case 1:
+ {
+ return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1);
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!(NewState.RightShoulder1 && !OldState.RightShoulder1);
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!(NewState.LeftShock && !OldState.LeftShock);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+bool CPad::GetCarGunFired(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return !!NewState.Circle;
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!NewState.RightShoulder1;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::CarGunJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return !!(NewState.Circle && !OldState.Circle);
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!(NewState.RightShoulder1 && !OldState.RightShoulder1);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+int16 CPad::GetHandBrake(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ {
+ return NewState.RightShoulder1;
+
+ break;
+ }
+
+ case 2:
+ {
+ return NewState.Triangle;
+
+ break;
+ }
+
+ case 3:
+ {
+ return NewState.LeftShoulder1;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int16 CPad::GetBrake(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ return NewState.Square;
+
+ break;
+ }
+
+ case 1:
+ {
+ return NewState.Square;
+
+ break;
+ }
+
+ case 3:
+ {
+ int16 axis = 2 * NewState.RightStickY;
+
+ if ( axis < 0 )
+ return 0;
+ else
+ return axis;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+bool CPad::GetExitVehicle(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 3:
+ {
+ return !!NewState.Triangle;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.LeftShoulder1;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::ExitVehicleJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 3:
+ {
+ return !!(NewState.Triangle && !OldState.Triangle);
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+int32 CPad::GetWeapon(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ {
+ return NewState.Circle;
+
+ break;
+ }
+
+ case 2:
+ {
+ return NewState.Cross;
+
+ break;
+ }
+
+ case 3:
+ {
+ return NewState.RightShoulder1;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::WeaponJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ {
+ return !!(NewState.Circle && !OldState.Circle);
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!(NewState.Cross && !OldState.Cross);
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!(NewState.RightShoulder1 && !OldState.RightShoulder1);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+int16 CPad::GetAccelerate(void)
+{
+ if ( DisablePlayerControls )
+ return 0;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ {
+ return NewState.Cross;
+
+ break;
+ }
+
+ case 1:
+ {
+ return NewState.Cross;
+
+ break;
+ }
+
+ case 3:
+ {
+ int16 axis = -2 * NewState.RightStickY;
+
+ if ( axis < 0 )
+ return 0;
+ else
+ return axis;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+bool CPad::CycleCameraModeUpJustDown(void)
+{
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ case 3:
+ {
+ return !!(NewState.Select && !OldState.Select);
+
+ break;
+ }
+
+ case 1:
+ {
+ return !!(NewState.DPadUp && !OldState.DPadUp);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::CycleCameraModeDownJustDown(void)
+{
+ switch ( Mode )
+ {
+ case 0:
+ case 2:
+ case 3:
+ {
+ return false;
+
+ break;
+ }
+
+ case 1:
+ {
+ return !!(NewState.DPadDown && !OldState.DPadDown);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::ChangeStationJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ {
+ return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1);
+
+ break;
+ }
+
+ case 1:
+ {
+ return !!(NewState.Select && !OldState.Select);
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!(NewState.LeftShock && !OldState.LeftShock);
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!(NewState.Circle && !OldState.Circle);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+bool CPad::CycleWeaponLeftJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2);
+}
+
+bool CPad::CycleWeaponRightJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.RightShoulder2 && !OldState.RightShoulder2);
+}
+
+bool CPad::GetTarget(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return !!NewState.RightShoulder1;
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!NewState.LeftShoulder1;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::TargetJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 2:
+ {
+ return !!(NewState.RightShoulder1 && !OldState.RightShoulder1);
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1);
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::JumpJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.Square && !OldState.Square);
+}
+
+bool CPad::GetSprint(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 3:
+ {
+ return !!NewState.Cross;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.Circle;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::ShiftTargetLeftJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2);
+}
+
+bool CPad::ShiftTargetRightJustDown(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ return !!(NewState.RightShoulder2 && !OldState.RightShoulder2);
+}
+
+bool CPad::GetAnaloguePadUp(void)
+{
+ static int16 oldfStickY = 0;
+
+ int16 Y = CPad::GetPad(0)->GetAnalogueUpDown();
+
+ if ( Y < 0 && oldfStickY >= 0 )
+ {
+ oldfStickY = Y;
+ return true;
+ }
+ else
+ {
+ oldfStickY = Y;
+ return false;
+ }
+}
+
+bool CPad::GetAnaloguePadDown(void)
+{
+ static int16 oldfStickY = 0;
+
+ int16 Y = CPad::GetPad(0)->GetAnalogueUpDown();
+
+ if ( Y > 0 && oldfStickY <= 0 )
+ {
+ oldfStickY = Y;
+ return true;
+ }
+ else
+ {
+ oldfStickY = Y;
+ return false;
+ }
+}
+
+bool CPad::GetAnaloguePadLeft(void)
+{
+ static int16 oldfStickX = 0;
+
+ int16 X = CPad::GetPad(0)->GetPedWalkLeftRight();
+
+ if ( X < 0 && oldfStickX >= 0 )
+ {
+ oldfStickX = X;
+ return true;
+ }
+ else
+ {
+ oldfStickX = X;
+ return false;
+ }
+}
+
+bool CPad::GetAnaloguePadRight(void)
+{
+ static int16 oldfStickX = 0;
+
+ int16 X = CPad::GetPad(0)->GetPedWalkLeftRight();
+
+ if ( X > 0 && oldfStickX <= 0 )
+ {
+ oldfStickX = X;
+ return true;
+ }
+ else
+ {
+ oldfStickX = X;
+ return false;
+ }
+}
+
+bool CPad::GetAnaloguePadLeftJustUp(void)
+{
+ static int16 oldfStickX = 0;
+
+ int16 X = GetPad(0)->GetPedWalkLeftRight();
+
+ if ( X == 0 && oldfStickX < 0 )
+ {
+ oldfStickX = X;
+
+ return true;
+ }
+ else
+ {
+ oldfStickX = X;
+
+ return false;
+ }
+}
+
+bool CPad::GetAnaloguePadRightJustUp(void)
+{
+ static int16 oldfStickX = 0;
+
+ int16 X = GetPad(0)->GetPedWalkLeftRight();
+
+ if ( X == 0 && oldfStickX > 0 )
+ {
+ oldfStickX = X;
+
+ return true;
+ }
+ else
+ {
+ oldfStickX = X;
+
+ return false;
+ }
+}
+
+bool CPad::ForceCameraBehindPlayer(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ {
+ return !!NewState.LeftShoulder1;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.Triangle;
+
+ break;
+ }
+
+ case 3:
+ {
+ return !!NewState.Circle;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::SniperZoomIn(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 3:
+ {
+ return !!NewState.Square;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.Triangle;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CPad::SniperZoomOut(void)
+{
+ if ( DisablePlayerControls )
+ return false;
+
+ switch ( Mode )
+ {
+ case 0:
+ case 1:
+ case 3:
+ {
+ return !!NewState.Cross;
+
+ break;
+ }
+
+ case 2:
+ {
+ return !!NewState.Square;
+
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+int16 CPad::SniperModeLookLeftRight(void)
+{
+ int16 axis = NewState.LeftStickX;
+ int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+}
+
+int16 CPad::SniperModeLookUpDown(void)
+{
+ int16 axis = NewState.LeftStickY;
+ int16 dpad = (NewState.DPadUp - NewState.DPadDown) / 2;
+
+ if ( abs(axis) > abs(dpad) )
+ return axis;
+ else
+ return dpad;
+}
+
+int16 CPad::LookAroundLeftRight(void)
+{
+ float axis = GetPad(0)->NewState.RightStickX;
+
+ if ( fabs(axis) > 85 && !GetLookBehindForPed() )
+ return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) )
+ * (127.0f / 32.0f) ); // 3.96875f
+
+ else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && fabs(axis) > 10 )
+ return (int16) ( (axis + ( ( axis > 0 ) ? -10 : 10) )
+ * (127.0f / 64.0f) ); // 1.984375f
+
+ return 0;
+}
+
+int16 CPad::LookAroundUpDown(void)
+{
+ int16 axis = GetPad(0)->NewState.RightStickY;
+
+ if ( abs(axis) > 85 && !GetLookBehindForPed() )
+ return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) )
+ * (127.0f / 32.0f) ); // 3.96875f
+
+ else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && abs(axis) > 40 )
+ return (int16) ( (axis + ( ( axis > 0 ) ? -40 : 40) )
+ * (127.0f / 64.0f) ); // 1.984375f
+
+ return 0;
+}
+
+
+void CPad::ResetAverageWeapon(void)
+{
+ AverageWeapon = GetWeapon();
+ AverageEntries = 1;
+}
+
+void CPad::PrintErrorMessage(void)
+{
+ if ( bDisplayNoControllerMessage && !CGame::playingIntro && !FrontEndMenuManager.m_bMenuActive )
+ {
+ CFont::SetScale(0.85f, 1.0f);
+ CFont::SetJustifyOff();
+ CFont::SetBackgroundOff();
+ CFont::SetCentreSize(SCREEN_WIDTH - 20);
+ CFont::SetCentreOn();
+ CFont::SetPropOn();
+ CFont::SetColor(CRGBA(255, 255, 200, 200));
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::PrintString
+ (
+ SCREEN_WIDTH / 2,
+ SCREEN_HEIGHT / 2,
+ TheText.Get("NOCONT") // Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue
+ );
+ }
+ else if ( bObsoleteControllerMessage )
+ {
+ CFont::SetScale(0.85f, 1.0f);
+ CFont::SetJustifyOff();
+ CFont::SetBackgroundOff();
+ CFont::SetCentreSize(SCREEN_WIDTH - 20);
+ CFont::SetCentreOn();
+ CFont::SetPropOn();
+ CFont::SetColor(CRGBA(255, 255, 200, 200));
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::PrintString
+ (
+ SCREEN_WIDTH / 2,
+ SCREEN_HEIGHT / 2,
+ TheText.Get("WRCONT") // The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2).
+ );
+ }
+
+}
+
+void LittleTest(void)
+{
+ static int32 Cunt = 0;
+
+ Cunt++; // ???
+}
+
+void CPad::ResetCheats(void)
+{
+ CWeather::ReleaseWeather();
+
+ CPopulation::ms_bGivePedsWeapons = false;
+
+ CPed::bNastyLimbsCheat = false;
+ CPed::bPedCheat2 = false;
+ CPed::bPedCheat3 = false;
+
+ CVehicle::bWheelsOnlyCheat = false;
+ CVehicle::bAllDodosCheat = false;
+ CVehicle::bCheat3 = false;
+ CVehicle::bCheat4 = false;
+ CVehicle::bCheat5 = false;
+
+ gbFastTime = false;
+ CTimer::SetTimeScale(1.0f);
+}
+
+char *CPad::EditString(char *pStr, int32 nSize)
+{
+ int32 pos = strlen(pStr);
+
+ // letters
+ for ( int32 i = 0; i < ('Z' - 'A' + 1); i++ )
+ {
+ if ( GetPad(0)->GetCharJustDown(i + 'A') && pos < nSize - 1 )
+ {
+ pStr[pos++] = i + 'A';
+ pStr[pos] = '\0';
+ }
+
+ if ( GetPad(0)->GetCharJustDown(i + 'a') && pos < nSize - 1 )
+ {
+ pStr[pos++] = i + 'a';
+ pStr[pos] = '\0';
+ }
+ }
+
+ // numbers
+ for ( int32 i = 0; i < ('0' - '9' + 1); i++ )
+ {
+ if ( GetPad(0)->GetCharJustDown(i + '0') && pos < nSize - 1 )
+ {
+ pStr[pos++] = i + '0';
+ pStr[pos] = '\0';
+ }
+ }
+
+ // space
+ if ( GetPad(0)->GetCharJustDown(' ') && pos < nSize - 1 )
+ {
+ pStr[pos++] = ' ';
+ pStr[pos] = '\0';
+ }
+
+
+ // del
+ if ( GetPad(0)->GetDeleteJustDown() || GetPad(0)->GetBackspaceJustDown() )
+ {
+ if ( pos > 0 )
+ pStr[pos - 1] = '\0';
+ }
+
+ // extenter/up/down
+ if ( GetPad(0)->GetEnterJustDown() || GetPad(0)->GetUpJustDown() || GetPad(0)->GetDownJustDown() )
+ return nil;
+
+ return pStr;
+}
+
+int32 *CPad::EditCodesForControls(int32 *pRsKeys, int32 nSize)
+{
+ *pRsKeys = rsNULL;
+
+ for ( int32 i = 0; i < 255; i++ )
+ {
+ if ( GetPad(0)->GetCharJustDown(i) )
+ *pRsKeys = i;
+ }
+
+ for ( int32 i = 0; i < 12; i++ )
+ {
+ if ( GetPad(0)->GetFJustDown(i) )
+ *pRsKeys = i + rsF1;
+ }
+
+ if ( GetPad(0)->GetEscapeJustDown() )
+ *pRsKeys = rsESC;
+
+ if ( GetPad(0)->GetInsertJustDown() )
+ *pRsKeys = rsINS;
+
+ if ( GetPad(0)->GetDeleteJustDown() )
+ *pRsKeys = rsDEL;
+
+ if ( GetPad(0)->GetHomeJustDown() )
+ *pRsKeys = rsHOME;
+
+ if ( GetPad(0)->GetEndJustDown() )
+ *pRsKeys = rsEND;
+
+ if ( GetPad(0)->GetPageUpJustDown() )
+ *pRsKeys = rsPGUP;
+
+ if ( GetPad(0)->GetPageDownJustDown() )
+ *pRsKeys = rsPGDN;
+
+ if ( GetPad(0)->GetUpJustDown() )
+ *pRsKeys = rsUP;
+
+ if ( GetPad(0)->GetDownJustDown() )
+ *pRsKeys = rsDOWN;
+
+ if ( GetPad(0)->GetLeftJustDown() )
+ *pRsKeys = rsLEFT;
+
+ if ( GetPad(0)->GetRightJustDown() )
+ *pRsKeys = rsRIGHT;
+
+ if ( GetPad(0)->GetScrollLockJustDown() )
+ *pRsKeys = rsSCROLL;
+
+ if ( GetPad(0)->GetPauseJustDown() )
+ *pRsKeys = rsPAUSE;
+
+ if ( GetPad(0)->GetNumLockJustDown() )
+ *pRsKeys = rsNUMLOCK;
+
+ if ( GetPad(0)->GetDivideJustDown() )
+ *pRsKeys = rsDIVIDE;
+
+ if ( GetPad(0)->GetTimesJustDown() )
+ *pRsKeys = rsTIMES;
+
+ if ( GetPad(0)->GetMinusJustDown() )
+ *pRsKeys = rsMINUS;
+
+ if ( GetPad(0)->GetPlusJustDown() )
+ *pRsKeys = rsPLUS;
+
+ if ( GetPad(0)->GetPadEnterJustDown() )
+ *pRsKeys = rsPADENTER;
+
+ if ( GetPad(0)->GetPadDelJustDown() )
+ *pRsKeys = rsPADDEL;
+
+ if ( GetPad(0)->GetPad1JustDown() )
+ *pRsKeys = rsPADEND;
+
+ if ( GetPad(0)->GetPad2JustDown() )
+ *pRsKeys = rsPADDOWN;
+
+ if ( GetPad(0)->GetPad3JustDown() )
+ *pRsKeys = rsPADPGDN;
+
+ if ( GetPad(0)->GetPad4JustDown() )
+ *pRsKeys = rsPADLEFT;
+
+ if ( GetPad(0)->GetPad5JustDown() )
+ *pRsKeys = rsPAD5;
+
+ if ( GetPad(0)->GetPad6JustDown() )
+ *pRsKeys = rsPADRIGHT;
+
+ if ( GetPad(0)->GetPad7JustDown() )
+ *pRsKeys = rsPADHOME;
+
+ if ( GetPad(0)->GetPad8JustDown() )
+ *pRsKeys = rsPADUP;
+
+ if ( GetPad(0)->GetPad9JustDown() )
+ *pRsKeys = rsPADPGUP;
+
+ if ( GetPad(0)->GetPad0JustDown() )
+ *pRsKeys = rsPADINS;
+
+ if ( GetPad(0)->GetBackspaceJustDown() )
+ *pRsKeys = rsBACKSP;
+
+ if ( GetPad(0)->GetTabJustDown() )
+ *pRsKeys = rsTAB;
+
+ if ( GetPad(0)->GetCapsLockJustDown() )
+ *pRsKeys = rsCAPSLK;
+
+ if ( GetPad(0)->GetEnterJustDown() )
+ *pRsKeys = rsENTER;
+
+ if ( GetPad(0)->GetLeftShiftJustDown() )
+ *pRsKeys = rsLSHIFT;
+
+ if ( GetPad(0)->GetShiftJustDown() )
+ *pRsKeys = rsSHIFT;
+
+ if ( GetPad(0)->GetRightShiftJustDown() )
+ *pRsKeys = rsRSHIFT;
+
+ if ( GetPad(0)->GetLeftCtrlJustDown() )
+ *pRsKeys = rsLCTRL;
+
+ if ( GetPad(0)->GetRightCtrlJustDown() )
+ *pRsKeys = rsRCTRL;
+
+ if ( GetPad(0)->GetLeftAltJustDown() )
+ *pRsKeys = rsLALT;
+
+ if ( GetPad(0)->GetRightAltJustDown() )
+ *pRsKeys = rsRALT;
+
+ if ( GetPad(0)->GetLeftWinJustDown() )
+ *pRsKeys = rsLWIN;
+
+ if ( GetPad(0)->GetRightWinJustDown() )
+ *pRsKeys = rsRWIN;
+
+ if ( GetPad(0)->GetAppsJustDown() )
+ *pRsKeys = rsAPPS;
+
+ return pRsKeys;
+}
+
+STARTPATCHES
+ InjectHook(0x4916C0, &CControllerState::Clear, PATCH_JUMP);
+ InjectHook(0x491760, &CKeyboardState::Clear, PATCH_JUMP);
+ InjectHook(0x491A10, &CPad::Clear, PATCH_JUMP);
+ InjectHook(0x491B50, &CPad::ClearMouseHistory, PATCH_JUMP);
+ //InjectHook(0x491B80, &CMouseControllerState::CMouseControllerState, PATCH_JUMP);
+ InjectHook(0x491BB0, &CMouseControllerState::Clear, PATCH_JUMP);
+ InjectHook(0x491BD0, &CMousePointerStateHelper::GetMouseSetUp, PATCH_JUMP);
+ InjectHook(0x491CA0, &CPad::UpdateMouse, PATCH_JUMP);
+ InjectHook(0x491E60, &CPad::ReconcileTwoControllersInput, PATCH_JUMP);
+ InjectHook(0x492230, &CPad::StartShake, PATCH_JUMP);
+ InjectHook(0x492290, &CPad::StartShake_Distance, PATCH_JUMP);
+ InjectHook(0x492360, &CPad::StartShake_Train, PATCH_JUMP);
+ InjectHook(0x492450, &CPad::AddToPCCheatString, PATCH_JUMP);
+ InjectHook(0x492720, CPad::UpdatePads, PATCH_JUMP);
+ InjectHook(0x492C60, &CPad::ProcessPCSpecificStuff, PATCH_JUMP);
+ InjectHook(0x492C70, &CPad::Update, PATCH_JUMP);
+#pragma warning( push )
+#pragma warning( disable : 4573)
+ InjectHook(0x492F00, (void (*)())CPad::DoCheats, PATCH_JUMP);
+#pragma warning( pop )
+ InjectHook(0x492F20, (void (CPad::*)(int16))&CPad::DoCheats, PATCH_JUMP);
+ InjectHook(0x492F30, CPad::StopPadsShaking, PATCH_JUMP);
+ InjectHook(0x492F50, &CPad::StopShaking, PATCH_JUMP);
+ InjectHook(0x492F60, CPad::GetPad, PATCH_JUMP);
+ InjectHook(0x492F70, &CPad::GetSteeringLeftRight, PATCH_JUMP);
+ InjectHook(0x492FF0, &CPad::GetSteeringUpDown, PATCH_JUMP);
+ InjectHook(0x493070, &CPad::GetCarGunUpDown, PATCH_JUMP);
+ InjectHook(0x4930C0, &CPad::GetCarGunLeftRight, PATCH_JUMP);
+ InjectHook(0x493110, &CPad::GetPedWalkLeftRight, PATCH_JUMP);
+ InjectHook(0x493190, &CPad::GetPedWalkUpDown, PATCH_JUMP);
+ InjectHook(0x493210, &CPad::GetAnalogueUpDown, PATCH_JUMP);
+ InjectHook(0x493290, &CPad::GetLookLeft, PATCH_JUMP);
+ InjectHook(0x4932C0, &CPad::GetLookRight, PATCH_JUMP);
+ InjectHook(0x4932F0, &CPad::GetLookBehindForCar, PATCH_JUMP);
+ InjectHook(0x493320, &CPad::GetLookBehindForPed, PATCH_JUMP);
+ InjectHook(0x493350, &CPad::GetHorn, PATCH_JUMP);
+ InjectHook(0x4933F0, &CPad::HornJustDown, PATCH_JUMP);
+ InjectHook(0x493490, &CPad::GetCarGunFired, PATCH_JUMP);
+ InjectHook(0x4934F0, &CPad::CarGunJustDown, PATCH_JUMP);
+ InjectHook(0x493560, &CPad::GetHandBrake, PATCH_JUMP);
+ InjectHook(0x4935A0, &CPad::GetBrake, PATCH_JUMP);
+ InjectHook(0x4935F0, &CPad::GetExitVehicle, PATCH_JUMP);
+ InjectHook(0x493650, &CPad::ExitVehicleJustDown, PATCH_JUMP);
+ InjectHook(0x4936C0, &CPad::GetWeapon, PATCH_JUMP);
+ InjectHook(0x493700, &CPad::WeaponJustDown, PATCH_JUMP);
+ InjectHook(0x493780, &CPad::GetAccelerate, PATCH_JUMP);
+ InjectHook(0x4937D0, &CPad::CycleCameraModeUpJustDown, PATCH_JUMP);
+ InjectHook(0x493830, &CPad::CycleCameraModeDownJustDown, PATCH_JUMP);
+ InjectHook(0x493870, &CPad::ChangeStationJustDown, PATCH_JUMP);
+ InjectHook(0x493910, &CPad::CycleWeaponLeftJustDown, PATCH_JUMP);
+ InjectHook(0x493940, &CPad::CycleWeaponRightJustDown, PATCH_JUMP);
+ InjectHook(0x493970, &CPad::GetTarget, PATCH_JUMP);
+ InjectHook(0x4939D0, &CPad::TargetJustDown, PATCH_JUMP);
+ InjectHook(0x493A40, &CPad::JumpJustDown, PATCH_JUMP);
+ InjectHook(0x493A70, &CPad::GetSprint, PATCH_JUMP);
+ InjectHook(0x493AE0, &CPad::ShiftTargetLeftJustDown, PATCH_JUMP);
+ InjectHook(0x493B10, &CPad::ShiftTargetRightJustDown, PATCH_JUMP);
+ InjectHook(0x493B40, &CPad::GetAnaloguePadUp, PATCH_JUMP);
+ InjectHook(0x493BA0, &CPad::GetAnaloguePadDown, PATCH_JUMP);
+ InjectHook(0x493C00, &CPad::GetAnaloguePadLeft, PATCH_JUMP);
+ InjectHook(0x493C60, &CPad::GetAnaloguePadRight, PATCH_JUMP);
+ InjectHook(0x493CC0, &CPad::GetAnaloguePadLeftJustUp, PATCH_JUMP);
+ InjectHook(0x493D20, &CPad::GetAnaloguePadRightJustUp, PATCH_JUMP);
+ InjectHook(0x493D80, &CPad::ForceCameraBehindPlayer, PATCH_JUMP);
+ InjectHook(0x493E00, &CPad::SniperZoomIn, PATCH_JUMP);
+ InjectHook(0x493E70, &CPad::SniperZoomOut, PATCH_JUMP);
+ InjectHook(0x493EE0, &CPad::SniperModeLookLeftRight, PATCH_JUMP);
+ InjectHook(0x493F30, &CPad::SniperModeLookUpDown, PATCH_JUMP);
+ InjectHook(0x493F80, &CPad::LookAroundLeftRight, PATCH_JUMP);
+ InjectHook(0x494130, &CPad::LookAroundUpDown, PATCH_JUMP);
+ InjectHook(0x494290, &CPad::ResetAverageWeapon, PATCH_JUMP);
+ InjectHook(0x4942B0, CPad::PrintErrorMessage, PATCH_JUMP);
+ InjectHook(0x494420, LittleTest, PATCH_JUMP);
+ InjectHook(0x494450, CPad::ResetCheats, PATCH_JUMP);
+ InjectHook(0x4944B0, CPad::EditString, PATCH_JUMP);
+ InjectHook(0x494690, CPad::EditCodesForControls, PATCH_JUMP);
+
+ //InjectHook(0x494E50, `global constructor keyed to'Pad.cpp, PATCH_JUMP);
+ //InjectHook(0x494EB0, sub_494EB0, PATCH_JUMP);
+ //InjectHook(0x494ED0, &CPad::~CPad, PATCH_JUMP);
+ //InjectHook(0x494EE0, &CPad::CPad, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Pad.h b/src/core/Pad.h
new file mode 100644
index 00000000..30cdb8df
--- /dev/null
+++ b/src/core/Pad.h
@@ -0,0 +1,367 @@
+#pragma once
+
+// same as RW skeleton
+/*
+enum Key
+{
+ // ascii...
+
+ KEY_ESC = 128,
+
+ KEY_F1 = 129,
+ KEY_F2 = 130,
+ KEY_F3 = 131,
+ KEY_F4 = 132,
+ KEY_F5 = 133,
+ KEY_F6 = 134,
+ KEY_F7 = 135,
+ KEY_F8 = 136,
+ KEY_F9 = 137,
+ KEY_F10 = 138,
+ KEY_F11 = 139,
+ KEY_F12 = 140,
+
+ KEY_INS = 141,
+ KEY_DEL = 142,
+ KEY_HOME = 143,
+ KEY_END = 144,
+ KEY_PGUP = 145,
+ KEY_PGDN = 146,
+
+ KEY_UP = 147,
+ KEY_DOWN = 148,
+ KEY_LEFT = 149,
+ KEY_RIGHT = 150,
+
+ // some stuff ommitted
+
+ KEY_BACKSP = 168,
+ KEY_TAB = 169,
+ KEY_CAPSLK = 170,
+ KEY_ENTER = 171,
+ KEY_LSHIFT = 172,
+ KEY_RSHIFT = 173,
+ KEY_LCTRL = 174,
+ KEY_RCTRL = 175,
+ KEY_LALT = 176,
+ KEY_RALT = 177,
+
+ KEY_NULL, // unused
+ KEY_NUMKEYS
+};
+*/
+
+
+class CControllerState
+{
+public:
+ int16 LeftStickX, LeftStickY;
+ int16 RightStickX, RightStickY;
+ int16 LeftShoulder1, LeftShoulder2;
+ int16 RightShoulder1, RightShoulder2;
+ int16 DPadUp, DPadDown, DPadLeft, DPadRight;
+ int16 Start, Select;
+ int16 Square, Triangle, Cross, Circle;
+ int16 LeftShock, RightShock;
+ int16 NetworkTalk;
+ float GetLeftStickX(void) { return LeftStickX/32767.0f; };
+ float GetLeftStickY(void) { return LeftStickY/32767.0f; };
+ float GetRightStickX(void) { return RightStickX/32767.0f; };
+ float GetRightStickY(void) { return RightStickY/32767.0f; };
+
+ void Clear(void);
+};
+VALIDATE_SIZE(CControllerState, 0x2A);
+
+class CMouseControllerState
+{
+public:
+ //uint32 btns; // bit 0-2 button 1-3
+
+ bool LMB;
+ bool RMB;
+ bool MMB;
+ bool WHEELUP;
+ bool WHEELDN;
+ bool MXB1;
+ bool MXB2;
+ char _pad0;
+
+ float x, y;
+
+ CMouseControllerState();
+ void Clear();
+};
+
+VALIDATE_SIZE(CMouseControllerState, 0x10);
+
+class CMousePointerStateHelper
+{
+public:
+ bool bInvertHorizontally;
+ bool bInvertVertically;
+
+ CMouseControllerState GetMouseSetUp();
+};
+
+VALIDATE_SIZE(CMousePointerStateHelper, 0x2);
+
+extern CMousePointerStateHelper &MousePointerStateHelper;
+
+
+class CKeyboardState
+{
+public:
+ int16 F[12];
+ int16 VK_KEYS[256];
+ int16 ESC;
+ int16 INS;
+ int16 DEL;
+ int16 HOME;
+ int16 END;
+ int16 PGUP;
+ int16 PGDN;
+ int16 UP;
+ int16 DOWN;
+ int16 LEFT;
+ int16 RIGHT;
+ int16 SCROLLLOCK;
+ int16 PAUSE;
+ int16 NUMLOCK;
+ int16 DIV;
+ int16 MUL;
+ int16 SUB;
+ int16 ADD;
+ int16 ENTER;
+ int16 DECIMAL;
+ int16 NUM1;
+ int16 NUM2;
+ int16 NUM3;
+ int16 NUM4;
+ int16 NUM5;
+ int16 NUM6;
+ int16 NUM7;
+ int16 NUM8;
+ int16 NUM9;
+ int16 NUM0;
+ int16 BACKSP;
+ int16 TAB;
+ int16 CAPSLOCK;
+ int16 EXTENTER;
+ int16 LSHIFT;
+ int16 RSHIFT;
+ int16 SHIFT;
+ int16 LCTRL;
+ int16 RCTRL;
+ int16 LALT;
+ int16 RALT;
+ int16 LWIN;
+ int16 RWIN;
+ int16 APPS;
+
+ void Clear();
+};
+
+VALIDATE_SIZE(CKeyboardState, 0x270);
+
+enum
+{
+ // taken from miss2
+ PAD1 = 0,
+ PAD2,
+
+ MAX_PADS
+};
+
+class CPad
+{
+public:
+ CControllerState NewState;
+ CControllerState OldState;
+ CControllerState PCTempKeyState;
+ CControllerState PCTempJoyState;
+ CControllerState PCTempMouseState;
+ // straight out of my IDB
+ int16 Phase;
+ int16 Mode;
+ int16 ShakeDur;
+ uint8 ShakeFreq;
+ int8 bHornHistory[5];
+ uint8 iCurrHornHistory;
+ bool DisablePlayerControls;
+ int8 bApplyBrakes;
+ char _unk[12]; //int32 unk[3];
+ char _pad0[3];
+ int32 LastTimeTouched;
+ int32 AverageWeapon;
+ int32 AverageEntries;
+
+ CPad() { }
+ ~CPad() { }
+
+ static bool &bDisplayNoControllerMessage;
+ static bool &bObsoleteControllerMessage;
+ static bool &m_bMapPadOneToPadTwo;
+
+ static CKeyboardState &OldKeyState;
+ static CKeyboardState &NewKeyState;
+ static CKeyboardState &TempKeyState;
+ static char KeyBoardCheatString[18];
+ static CMouseControllerState &OldMouseControllerState;
+ static CMouseControllerState &NewMouseControllerState;
+ static CMouseControllerState &PCTempMouseControllerState;
+
+
+
+
+ void Clear(bool bResetPlayerControls);
+ void ClearMouseHistory();
+ void UpdateMouse();
+ CControllerState ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2);
+ void StartShake(int16 nDur, uint8 nFreq);
+ void StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fz);
+ void StartShake_Train(float fX, float fY);
+ void AddToPCCheatString(char c);
+
+ static void UpdatePads(void);
+ void ProcessPCSpecificStuff(void);
+ void Update(int16 unk);
+
+ static void DoCheats(void);
+ void DoCheats(int16 unk);
+
+ static void StopPadsShaking(void);
+ void StopShaking(int16 unk);
+
+ static CPad *GetPad(int32 pad);
+
+ int16 GetSteeringLeftRight(void);
+ int16 GetSteeringUpDown(void);
+ int16 GetCarGunUpDown(void);
+ int16 GetCarGunLeftRight(void);
+ int16 GetPedWalkLeftRight(void);
+ int16 GetPedWalkUpDown(void);
+ int16 GetAnalogueUpDown(void);
+ bool GetLookLeft(void);
+ bool GetLookRight(void);
+ bool GetLookBehindForCar(void);
+ bool GetLookBehindForPed(void);
+ bool GetHorn(void);
+ bool HornJustDown(void);
+ bool GetCarGunFired(void);
+ bool CarGunJustDown(void);
+ int16 GetHandBrake(void);
+ int16 GetBrake(void);
+ bool GetExitVehicle(void);
+ bool ExitVehicleJustDown(void);
+ int32 GetWeapon(void);
+ bool WeaponJustDown(void);
+ int16 GetAccelerate(void);
+ bool CycleCameraModeUpJustDown(void);
+ bool CycleCameraModeDownJustDown(void);
+ bool ChangeStationJustDown(void);
+ bool CycleWeaponLeftJustDown(void);
+ bool CycleWeaponRightJustDown(void);
+ bool GetTarget(void);
+ bool TargetJustDown(void);
+ bool JumpJustDown(void);
+ bool GetSprint(void);
+ bool ShiftTargetLeftJustDown(void);
+ bool ShiftTargetRightJustDown(void);
+ bool GetAnaloguePadUp(void);
+ bool GetAnaloguePadDown(void);
+ bool GetAnaloguePadLeft(void);
+ bool GetAnaloguePadRight(void);
+ bool GetAnaloguePadLeftJustUp(void);
+ bool GetAnaloguePadRightJustUp(void);
+ bool ForceCameraBehindPlayer(void);
+ bool SniperZoomIn(void);
+ bool SniperZoomOut(void);
+ int16 SniperModeLookLeftRight(void);
+ int16 SniperModeLookUpDown(void);
+ int16 LookAroundLeftRight(void);
+ int16 LookAroundUpDown(void);
+ void ResetAverageWeapon(void);
+ static void PrintErrorMessage(void);
+ static void ResetCheats(void);
+ static char *EditString(char *pStr, int32 nSize);
+ static int32 *EditCodesForControls(int32 *pRsKeys, int32 nSize);
+
+ // mouse
+ bool GetLeftMouseJustDown() { return !!(NewMouseControllerState.LMB && !OldMouseControllerState.LMB); }
+
+ // keyboard
+
+ bool GetCharJustDown(int32 c) { return !!(NewKeyState.VK_KEYS[c] && !OldKeyState.VK_KEYS[c]); }
+ bool GetFJustDown(int32 n) { return !!(NewKeyState.F[n] && !OldKeyState.F[n]); }
+ bool GetEscapeJustDown() { return !!(NewKeyState.ESC && !OldKeyState.ESC); }
+ bool GetInsertJustDown() { return !!(NewKeyState.INS && !OldKeyState.INS); }
+ bool GetDeleteJustDown() { return !!(NewKeyState.DEL && !OldKeyState.DEL); }
+ bool GetHomeJustDown() { return !!(NewKeyState.HOME && !OldKeyState.HOME); }
+ bool GetEndJustDown() { return !!(NewKeyState.END && !OldKeyState.END); }
+ bool GetPageUpJustDown() { return !!(NewKeyState.PGUP && !OldKeyState.PGUP); }
+ bool GetPageDownJustDown() { return !!(NewKeyState.PGDN && !OldKeyState.PGDN); }
+ bool GetUpJustDown() { return !!(NewKeyState.UP && !OldKeyState.UP); }
+ bool GetDownJustDown() { return !!(NewKeyState.DOWN && !OldKeyState.DOWN); }
+ bool GetLeftJustDown() { return !!(NewKeyState.LEFT && !OldKeyState.LEFT); }
+ bool GetRightJustDown() { return !!(NewKeyState.RIGHT && !OldKeyState.RIGHT); }
+ bool GetScrollLockJustDown() { return !!(NewKeyState.SCROLLLOCK && !OldKeyState.SCROLLLOCK); }
+ bool GetPauseJustDown() { return !!(NewKeyState.PAUSE && !OldKeyState.PAUSE); }
+ bool GetNumLockJustDown() { return !!(NewKeyState.NUMLOCK && !OldKeyState.NUMLOCK); }
+ bool GetDivideJustDown() { return !!(NewKeyState.DIV && !OldKeyState.DIV); }
+ bool GetTimesJustDown() { return !!(NewKeyState.MUL && !OldKeyState.MUL); }
+ bool GetMinusJustDown() { return !!(NewKeyState.SUB && !OldKeyState.SUB); }
+ bool GetPlusJustDown() { return !!(NewKeyState.ADD && !OldKeyState.ADD); }
+ bool GetPadEnterJustDown() { return !!(NewKeyState.ENTER && !OldKeyState.ENTER); } // GetEnterJustDown
+ bool GetPadDelJustDown() { return !!(NewKeyState.DECIMAL && !OldKeyState.DECIMAL); }
+ bool GetPad1JustDown() { return !!(NewKeyState.NUM1 && !OldKeyState.NUM1); }
+ bool GetPad2JustDown() { return !!(NewKeyState.NUM2 && !OldKeyState.NUM2); }
+ bool GetPad3JustDown() { return !!(NewKeyState.NUM3 && !OldKeyState.NUM3); }
+ bool GetPad4JustDown() { return !!(NewKeyState.NUM4 && !OldKeyState.NUM4); }
+ bool GetPad5JustDown() { return !!(NewKeyState.NUM5 && !OldKeyState.NUM5); }
+ bool GetPad6JustDown() { return !!(NewKeyState.NUM6 && !OldKeyState.NUM6); }
+ bool GetPad7JustDown() { return !!(NewKeyState.NUM7 && !OldKeyState.NUM7); }
+ bool GetPad8JustDown() { return !!(NewKeyState.NUM8 && !OldKeyState.NUM8); }
+ bool GetPad9JustDown() { return !!(NewKeyState.NUM9 && !OldKeyState.NUM9); }
+ bool GetPad0JustDown() { return !!(NewKeyState.NUM0 && !OldKeyState.NUM0); }
+ bool GetBackspaceJustDown() { return !!(NewKeyState.BACKSP && !OldKeyState.BACKSP); }
+ bool GetTabJustDown() { return !!(NewKeyState.TAB && !OldKeyState.TAB); }
+ bool GetCapsLockJustDown() { return !!(NewKeyState.CAPSLOCK && !OldKeyState.CAPSLOCK); }
+ bool GetEnterJustDown() { return !!(NewKeyState.EXTENTER && !OldKeyState.EXTENTER); }
+ bool GetLeftShiftJustDown() { return !!(NewKeyState.LSHIFT && !OldKeyState.LSHIFT); }
+ bool GetShiftJustDown() { return !!(NewKeyState.SHIFT && !OldKeyState.SHIFT); }
+ bool GetRightShiftJustDown() { return !!(NewKeyState.RSHIFT && !OldKeyState.RSHIFT); }
+ bool GetLeftCtrlJustDown() { return !!(NewKeyState.LCTRL && !OldKeyState.LCTRL); }
+ bool GetRightCtrlJustDown() { return !!(NewKeyState.RCTRL && !OldKeyState.RCTRL); }
+ bool GetLeftAltJustDown() { return !!(NewKeyState.LALT && !OldKeyState.LALT); }
+ bool GetRightAltJustDown() { return !!(NewKeyState.RALT && !OldKeyState.RALT); }
+ bool GetLeftWinJustDown() { return !!(NewKeyState.LWIN && !OldKeyState.LWIN); }
+ bool GetRightWinJustDown() { return !!(NewKeyState.RWIN && !OldKeyState.RWIN); }
+ bool GetAppsJustDown() { return !!(NewKeyState.APPS && !OldKeyState.APPS); }
+
+ // pad
+
+ bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); }
+ bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); }
+ bool GetCrossJustDown() { return !!(NewState.Cross && !OldState.Cross); }
+ bool GetSquareJustDown() { return !!(NewState.Square && !OldState.Square); }
+ bool GetDPadUpJustDown() { return !!(NewState.DPadUp && !OldState.DPadUp); }
+ bool GetDPadDownJustDown() { return !!(NewState.DPadDown && !OldState.DPadDown); }
+ bool GetDPadLeftJustDown() { return !!(NewState.DPadLeft && !OldState.DPadLeft); }
+ bool GetDPadRightJustDown() { return !!(NewState.DPadRight && !OldState.DPadRight); }
+ bool GetLeftShoulder1JustDown() { return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); }
+ bool GetLeftShoulder2JustDown() { return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); }
+ bool GetRightShoulder1JustDown() { return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); }
+ bool GetRightShoulder2JustDown() { return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); }
+
+ int32 GetLeftShoulder1(void) { return NewState.LeftShoulder1; }
+ int32 GetLeftShoulder2(void) { return NewState.LeftShoulder2; }
+ int32 GetRightShoulder1(void) { return NewState.RightShoulder1; }
+ int32 GetRightShoulder2(void) { return NewState.RightShoulder2; }
+};
+VALIDATE_SIZE(CPad, 0xFC);
+
+#define IsButtonJustDown(pad, btn) \
+ (!(pad)->OldState.btn && (pad)->NewState.btn)
+
+void LittleTest(void);
diff --git a/src/core/Placeable.cpp b/src/core/Placeable.cpp
new file mode 100644
index 00000000..b4b2a37b
--- /dev/null
+++ b/src/core/Placeable.cpp
@@ -0,0 +1,72 @@
+#include "common.h"
+#include "Placeable.h"
+#include "patcher.h"
+
+CPlaceable::CPlaceable(void)
+{
+ m_matrix.SetScale(1.0f);
+}
+
+CPlaceable::~CPlaceable(void) { }
+
+void
+CPlaceable::SetHeading(float angle)
+{
+ CVector pos = GetPosition();
+ m_matrix.SetRotateZ(angle);
+ GetPosition() += pos;
+}
+
+bool
+CPlaceable::IsWithinArea(float x1, float y1, float x2, float y2)
+{
+ float tmp;
+
+ if(x1 > x2){
+ tmp = x1;
+ x1 = x2;
+ x2 = tmp;
+ }
+ if(y1 > y2){
+ tmp = y1;
+ y1 = y2;
+ y2 = tmp;
+ }
+
+ return x1 <= GetPosition().x && GetPosition().x <= x2 &&
+ y1 <= GetPosition().y && GetPosition().y <= y2;
+}
+
+bool
+CPlaceable::IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2)
+{
+ float tmp;
+
+ if(x1 > x2){
+ tmp = x1;
+ x1 = x2;
+ x2 = tmp;
+ }
+ if(y1 > y2){
+ tmp = y1;
+ y1 = y2;
+ y2 = tmp;
+ }
+ if(z1 > z2){
+ tmp = z1;
+ z1 = z2;
+ z2 = tmp;
+ }
+
+ return x1 <= GetPosition().x && GetPosition().x <= x2 &&
+ y1 <= GetPosition().y && GetPosition().y <= y2 &&
+ z1 <= GetPosition().z && GetPosition().z <= z2;
+}
+
+STARTPATCHES
+ InjectHook(0x49F9A0, &CPlaceable::ctor, PATCH_JUMP);
+ InjectHook(0x49F9E0, &CPlaceable::dtor, PATCH_JUMP);
+ InjectHook(0x49FA00, &CPlaceable::SetHeading, PATCH_JUMP);
+ InjectHook(0x49FA50, (bool (CPlaceable::*)(float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP);
+ InjectHook(0x49FAF0, (bool (CPlaceable::*)(float, float, float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Placeable.h b/src/core/Placeable.h
new file mode 100644
index 00000000..868ca9e7
--- /dev/null
+++ b/src/core/Placeable.h
@@ -0,0 +1,26 @@
+#pragma once
+
+class CPlaceable
+{
+public:
+ // disable allocation
+ static void *operator new(size_t) = delete;
+
+ CMatrix m_matrix;
+
+ CPlaceable(void);
+ virtual ~CPlaceable(void);
+ CVector &GetPosition(void) { return *m_matrix.GetPosition(); }
+ CVector &GetRight(void) { return *m_matrix.GetRight(); }
+ CVector &GetForward(void) { return *m_matrix.GetForward(); }
+ CVector &GetUp(void) { return *m_matrix.GetUp(); }
+ CMatrix &GetMatrix(void) { return m_matrix; }
+ void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); }
+ void SetHeading(float angle);
+ bool IsWithinArea(float x1, float y1, float x2, float y2);
+ bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2);
+
+ CPlaceable *ctor(void) { return ::new (this) CPlaceable(); }
+ void dtor(void) { this->CPlaceable::~CPlaceable(); }
+};
+static_assert(sizeof(CPlaceable) == 0x4C, "CPlaceable: error");
diff --git a/src/core/PlayerInfo.cpp b/src/core/PlayerInfo.cpp
new file mode 100644
index 00000000..59efe2ae
--- /dev/null
+++ b/src/core/PlayerInfo.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "PlayerInfo.h"
+
+WRAPPER void CPlayerInfo::MakePlayerSafe(bool) { EAXJMP(0x4A1400); } \ No newline at end of file
diff --git a/src/core/PlayerInfo.h b/src/core/PlayerInfo.h
new file mode 100644
index 00000000..e2b42fe7
--- /dev/null
+++ b/src/core/PlayerInfo.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "Collision.h"
+
+enum eWastedBustedState
+{
+ WBSTATE_PLAYING,
+ WBSTATE_WASTED,
+ WBSTATE_BUSTED,
+ WBSTATE_FAILED_CRITICAL_MISSION,
+};
+
+class CVehicle;
+class CPlayerPed;
+class CCivilianPed;
+
+class CPlayerInfo
+{
+public:
+ CPlayerPed *m_pPed;
+ CVehicle *m_pRemoteVehicle;
+ CColModel m_ColModel;
+ CVehicle *m_pVehicleEx;
+ char m_aPlayerName[70];
+ int32 m_nMoney;
+ int32 m_nVisibleMoney;
+ int32 m_nCollectedPackages;
+ int32 m_nTotalPackages;
+ int32 field_188;
+ int32 m_nSwitchTaxiTime;
+ bool m_bSwitchTaxi;
+ int8 field_197;
+ int8 field_198;
+ int8 field_199;
+ int32 m_nNextSexFrequencyUpdateTime;
+ int32 m_nNextSexMoneyUpdateTime;
+ int32 m_nSexFrequency;
+ CCivilianPed *m_pHooker;
+ int8 m_WBState; // eWastedBustedState
+ int8 field_217;
+ int8 field_218;
+ int8 field_219;
+ int32 m_nWBTime;
+ bool m_bInRemoteMode;
+ int8 field_225;
+ int8 field_226;
+ int8 field_227;
+ int32 m_nTimeLostRemoteCar;
+ int32 m_nTimeLastHealthLoss;
+ int32 m_nTimeLastArmourLoss;
+ int32 field_240;
+ int32 m_nUpsideDownCounter;
+ int32 field_248;
+ int16 m_nTrafficMultiplier;
+ int8 field_254;
+ int8 field_255;
+ float m_fRoadDensity;
+ int32 m_nPreviousTimeRewardedForExplosion;
+ int32 m_nExplosionsSinceLastReward;
+ int32 field_268;
+ int32 field_272;
+ bool m_bInfiniteSprint;
+ bool m_bFastReload;
+ bool m_bGetOutOfJailFree;
+ bool m_bGetOutOfHospitalFree;
+ uint8 m_aSkinName[32];
+ RwTexture *m_pSkinTexture;
+
+ void MakePlayerSafe(bool);
+};
+
+static_assert(sizeof(CPlayerInfo) == 0x13C, "CPlayerInfo: error");
diff --git a/src/core/PlayerSkin.cpp b/src/core/PlayerSkin.cpp
new file mode 100644
index 00000000..1c9ca2c6
--- /dev/null
+++ b/src/core/PlayerSkin.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "PlayerSkin.h"
+
+WRAPPER void CPlayerSkin::BeginFrontEndSkinEdit() { EAXJMP(0x59BC70); }
diff --git a/src/core/PlayerSkin.h b/src/core/PlayerSkin.h
new file mode 100644
index 00000000..61e09cdf
--- /dev/null
+++ b/src/core/PlayerSkin.h
@@ -0,0 +1,7 @@
+#pragma once
+
+class CPlayerSkin
+{
+public:
+ static void BeginFrontEndSkinEdit();
+}; \ No newline at end of file
diff --git a/src/core/Pools.cpp b/src/core/Pools.cpp
new file mode 100644
index 00000000..f7f93292
--- /dev/null
+++ b/src/core/Pools.cpp
@@ -0,0 +1,25 @@
+#include "common.h"
+#include "Pools.h"
+
+CCPtrNodePool *&CPools::ms_pPtrNodePool = *(CCPtrNodePool**)0x943044;
+CEntryInfoNodePool *&CPools::ms_pEntryInfoNodePool = *(CEntryInfoNodePool**)0x941448;
+CPedPool *&CPools::ms_pPedPool = *(CPedPool**)0x8F2C60;
+CVehiclePool *&CPools::ms_pVehiclePool = *(CVehiclePool**)0x9430DC;
+CBuildingPool *&CPools::ms_pBuildingPool = *(CBuildingPool**)0x8F2C04;
+CTreadablePool *&CPools::ms_pTreadablePool = *(CTreadablePool**)0x8F2568;
+CObjectPool *&CPools::ms_pObjectPool = *(CObjectPool**)0x880E28;
+CDummyPool *&CPools::ms_pDummyPool = *(CDummyPool**)0x8F2C18;
+
+void
+CPools::Initialise(void)
+{
+ // TODO: unused right now
+ ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES);
+ ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS);
+ ms_pPedPool = new CPedPool(NUMPEDS);
+ ms_pVehiclePool = new CVehiclePool(NUMVEHICLES);
+ ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS);
+ ms_pTreadablePool = new CTreadablePool(NUMTREADABLES);
+ ms_pObjectPool = new CObjectPool(NUMOBJECTS);
+ ms_pDummyPool = new CDummyPool(NUMDUMMIES);
+}
diff --git a/src/core/Pools.h b/src/core/Pools.h
new file mode 100644
index 00000000..3496064c
--- /dev/null
+++ b/src/core/Pools.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "templates.h"
+#include "Lists.h"
+#include "Treadable.h"
+#include "Object.h"
+#include "CutsceneHead.h"
+#include "PlayerPed.h"
+#include "Automobile.h"
+#include "DummyPed.h"
+
+typedef CPool<CPtrNode> CCPtrNodePool;
+typedef CPool<CEntryInfoNode> CEntryInfoNodePool;
+typedef CPool<CPed,CPlayerPed> CPedPool;
+typedef CPool<CVehicle,CAutomobile> CVehiclePool;
+typedef CPool<CBuilding> CBuildingPool;
+typedef CPool<CTreadable> CTreadablePool;
+typedef CPool<CObject, CCutsceneHead> CObjectPool;
+typedef CPool<CDummy, CDummyPed> CDummyPool;
+
+class CPools
+{
+ static CCPtrNodePool *&ms_pPtrNodePool;
+ static CEntryInfoNodePool *&ms_pEntryInfoNodePool;
+ static CPedPool *&ms_pPedPool;
+ static CVehiclePool *&ms_pVehiclePool;
+ static CBuildingPool *&ms_pBuildingPool;
+ static CTreadablePool *&ms_pTreadablePool;
+ static CObjectPool *&ms_pObjectPool;
+ static CDummyPool *&ms_pDummyPool;
+ // ms_pAudioScriptObjectPool
+public:
+ static CCPtrNodePool *GetPtrNodePool(void) { return ms_pPtrNodePool; }
+ static CEntryInfoNodePool *GetEntryInfoNodePool(void) { return ms_pEntryInfoNodePool; }
+ static CPedPool *GetPedPool(void) { return ms_pPedPool; }
+ static CVehiclePool *GetVehiclePool(void) { return ms_pVehiclePool; }
+ static CBuildingPool *GetBuildingPool(void) { return ms_pBuildingPool; }
+ static CTreadablePool *GetTreadablePool(void) { return ms_pTreadablePool; }
+ static CObjectPool *GetObjectPool(void) { return ms_pObjectPool; }
+ static CDummyPool *GetDummyPool(void) { return ms_pDummyPool; }
+
+ static void Initialise(void);
+};
diff --git a/src/core/Radar.cpp b/src/core/Radar.cpp
new file mode 100644
index 00000000..a071b96b
--- /dev/null
+++ b/src/core/Radar.cpp
@@ -0,0 +1,1117 @@
+#include "config.h"
+#include "common.h"
+#include "patcher.h"
+#include "RwHelper.h"
+#include "Radar.h"
+#include "Camera.h"
+#include "Hud.h"
+#include "World.h"
+#include "Frontend.h"
+#include "General.h"
+#include "Vehicle.h"
+#include "Pools.h"
+#include "Script.h"
+#include "TxdStore.h"
+#include "World.h"
+#include "Streaming.h"
+
+float &CRadar::m_RadarRange = *(float*)0x8E281C;
+CBlip *CRadar::ms_RadarTrace = (CBlip*)0x6ED5E0;
+
+CVector2D &vec2DRadarOrigin = *(CVector2D*)0x6299B8;
+int *gRadarTxdIds = (int*)0x6299C0;
+
+CSprite2d *CRadar::AsukaSprite = (CSprite2d*)0x8F1A40;
+CSprite2d *CRadar::BombSprite = (CSprite2d*)0x8F5FB4;
+CSprite2d *CRadar::CatSprite = (CSprite2d*)0x885B24;
+CSprite2d *CRadar::CentreSprite = (CSprite2d*)0x8F6268;
+CSprite2d *CRadar::CopcarSprite = (CSprite2d*)0x8F1A2C;
+CSprite2d *CRadar::DonSprite = (CSprite2d*)0x8F2BE0;
+CSprite2d *CRadar::EightSprite = (CSprite2d*)0x8F2BCC;
+CSprite2d *CRadar::ElSprite = (CSprite2d*)0x8F1B80;
+CSprite2d *CRadar::IceSprite = (CSprite2d*)0x9415FC;
+CSprite2d *CRadar::JoeySprite = (CSprite2d*)0x8F2C00;
+CSprite2d *CRadar::KenjiSprite = (CSprite2d*)0x8F2C68;
+CSprite2d *CRadar::LizSprite = (CSprite2d*)0x8F5830;
+CSprite2d *CRadar::LuigiSprite = (CSprite2d*)0x8F1A3C;
+CSprite2d *CRadar::NorthSprite = (CSprite2d*)0x8F6274;
+CSprite2d *CRadar::RaySprite = (CSprite2d*)0x8E2A7C;
+CSprite2d *CRadar::SalSprite = (CSprite2d*)0x8F29EC;
+CSprite2d *CRadar::SaveSprite = (CSprite2d*)0x8F5F74;
+CSprite2d *CRadar::SpraySprite = (CSprite2d*)0x94307C;
+CSprite2d *CRadar::TonySprite = (CSprite2d*)0x885B58;
+CSprite2d *CRadar::WeaponSprite = (CSprite2d*)0x941534;
+
+CSprite2d *CRadar::RadarSprites[RADAR_SPRITE_COUNT] = {
+ nil,
+ AsukaSprite,
+ BombSprite,
+ CatSprite,
+ CentreSprite,
+ CopcarSprite,
+ DonSprite,
+ EightSprite,
+ ElSprite,
+ IceSprite,
+ JoeySprite,
+ KenjiSprite,
+ LizSprite,
+ LuigiSprite,
+ NorthSprite,
+ RaySprite,
+ SalSprite,
+ SaveSprite,
+ SpraySprite,
+ TonySprite,
+ WeaponSprite
+};
+
+#define RADAR_NUM_TILES (8)
+#define RADAR_TILE_SIZE (WORLD_SIZE_X / RADAR_NUM_TILES)
+static_assert(RADAR_TILE_SIZE == (WORLD_SIZE_Y / RADAR_NUM_TILES), "CRadar: not a square");
+
+#define RADAR_MIN_RANGE (120.0f)
+#define RADAR_MAX_RANGE (350.0f)
+#define RADAR_MIN_SPEED (0.3f)
+#define RADAR_MAX_SPEED (0.9f)
+
+#if 0
+WRAPPER void CRadar::CalculateBlipAlpha(float) { EAXJMP(0x4A4F90); }
+#else
+int CRadar::CalculateBlipAlpha(float dist)
+{
+ if (dist <= 1.0f)
+ return 255;
+
+ if (dist <= 5.0f)
+ return (((1.0f - ((dist * 0.25f) - 0.25f)) * 255.0f) + (((dist * 0.25f) - 0.25f) * 128.0f));
+
+ return 128;
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::ChangeBlipBrightness(int32, int32) { EAXJMP(0x4A57A0); }
+#else
+void CRadar::ChangeBlipBrightness(int32 i, int32 bright)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::ChangeBlipColour(int32) { EAXJMP(0x4A5770); }
+#else
+void CRadar::ChangeBlipColour(int32 i)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::ChangeBlipDisplay(int32, int16) { EAXJMP(0x4A5810); }
+#else
+void CRadar::ChangeBlipDisplay(int32 i, int16 flag)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::ChangeBlipScale(int32, int16) { EAXJMP(0x4A57E0); }
+#else
+void CRadar::ChangeBlipScale(int32 i, int16 scale)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::ClearBlip(int32) { EAXJMP(0x4A5720); }
+#else
+void CRadar::ClearBlip(int32 i)
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::ClearBlipForEntity(int16, int32) { EAXJMP(0x4A56C0); }
+#else
+void CRadar::ClearBlipForEntity(int16 type, int32 id)
+{
+ for (int i = 0; i < NUMRADARBLIPS; i++) {
+ if (type == ms_RadarTrace[i].m_eBlipType && id == ms_RadarTrace[i].m_nEntityHandle) {
+ SetRadarMarkerState(i, 0);
+ ms_RadarTrace[i].m_bInUse = 0;
+ ms_RadarTrace[i].m_eBlipType = 0;
+ ms_RadarTrace[i].m_eBlipDisplay = 0;
+ ms_RadarTrace[i].m_IconID = 0;
+ }
+ };
+}
+#endif
+
+#if 0
+WRAPPER int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *in) { EAXJMP(0x4A64A0); }
+#else
+// Why not a proper clipping algorithm?
+int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect)
+{
+ CVector2D corners[4] = {
+ { 1.0f, -1.0f }, // top right
+ { 1.0f, 1.0f }, // bottom right
+ { -1.0f, 1.0f }, // bottom left
+ { -1.0f, -1.0f }, // top left
+ };
+ CVector2D tmp;
+ int i, j, n;
+ int laste, e, e1, e2;;
+ bool inside[4];
+
+ for (i = 0; i < 4; i++)
+ inside[i] = IsPointInsideRadar(rect[i]);
+
+ laste = -1;
+ n = 0;
+ for (i = 0; i < 4; i++)
+ if (inside[i]) {
+ // point is inside, just add
+ poly[n++] = rect[i];
+ }
+ else {
+ // point is outside but line to this point might be clipped
+ e1 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 4 - 1) % 4]);
+ if (e1 != -1) {
+ laste = e1;
+ n++;
+ }
+ // and line from this point might be clipped as well
+ e2 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 1) % 4]);
+ if (e2 != -1) {
+ if (e1 == -1) {
+ // if other line wasn't clipped, i.e. it was complete outside,
+ // we may have to insert another vertex if last clipped line
+ // was on a different edge
+
+ // find the last intersection if we haven't seen it yet
+ if (laste == -1)
+ for (j = 3; j >= i; j--) {
+ // game uses an if here for j == 0
+ e = LineRadarBoxCollision(tmp, rect[j], rect[(j + 4 - 1) % 4]);
+ if (e != -1) {
+ laste = e;
+ break;
+ }
+ }
+ assert(laste != -1);
+
+ // insert corners that were skipped
+ tmp = poly[n];
+ for (e = laste; e != e2; e = (e + 1) % 4)
+ poly[n++] = corners[e];
+ poly[n] = tmp;
+ }
+ n++;
+ }
+ }
+
+ if (n == 0) {
+ // If no points, either the rectangle is completely outside or completely surrounds the radar
+ // no idea what's going on here...
+ float m = (rect[0].y - rect[1].y) / (rect[0].x - rect[1].x);
+ if ((m*rect[3].x - rect[3].y) * (m*rect[0].x - rect[0].y) < 0.0f) {
+ m = (rect[0].y - rect[3].y) / (rect[0].x - rect[3].x);
+ if ((m*rect[1].x - rect[1].y) * (m*rect[0].x - rect[0].y) < 0.0f) {
+ poly[0] = corners[0];
+ poly[1] = corners[1];
+ poly[2] = corners[2];
+ poly[3] = corners[3];
+ n = 4;
+ }
+ }
+ }
+
+ return n;
+}
+#endif
+
+bool CRadar::DisplayThisBlip(int32 counter)
+{
+ switch (ms_RadarTrace[counter].m_IconID) {
+ case RADAR_SPRITE_BOMB:
+ case RADAR_SPRITE_SPRAY:
+ case RADAR_SPRITE_WEAPON:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#if 1
+WRAPPER void CRadar::Draw3dMarkers() { EAXJMP(0x4A4C70); }
+#else
+void CRadar::Draw3dMarkers()
+{
+
+}
+#endif
+
+
+#if 0
+WRAPPER void CRadar::DrawBlips() { EAXJMP(0x4A42F0); }
+#else
+void CRadar::DrawBlips()
+{
+ if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) {
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+
+ CVector2D out;
+ CVector2D in = CVector2D(0.0f, 0.0f);
+ TransformRadarPointToScreenSpace(out, in);
+
+ float angle;
+ if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1)
+ angle = PI + FindPlayerHeading();
+ else
+ angle = FindPlayerHeading() - (PI + TheCamera.GetForward().Heading());
+
+ DrawRotatingRadarSprite(CentreSprite, out.x, out.y, angle, 255);
+
+ CVector2D vec2d;
+ vec2d.x = vec2DRadarOrigin.x;
+ vec2d.y = M_SQRT2 * m_RadarRange + vec2DRadarOrigin.y;
+ TransformRealWorldPointToRadarSpace(in, vec2d);
+ LimitRadarPoint(in);
+ TransformRadarPointToScreenSpace(out, in);
+ DrawRadarSprite(RADAR_SPRITE_NORTH, out.x, out.y, 255);
+
+ /*
+ DrawEntityBlip
+ */
+ for (int i = 0; i < NUMRADARBLIPS; i++) {
+ if (ms_RadarTrace[i].m_bInUse) {
+ if (ms_RadarTrace[i].m_eBlipType <= BLIP_OBJECT) {
+ CEntity *e = nil;
+ switch (ms_RadarTrace[i].m_eBlipType) {
+ case BLIP_CAR:
+ e = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[i].m_nEntityHandle);
+ break;
+ case BLIP_CHAR:
+ e = CPools::GetPedPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle);
+ break;
+ case BLIP_OBJECT:
+ e = CPools::GetObjectPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle);
+ break;
+ };
+
+ if (e) {
+ if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) {
+ if (CTheScripts::DbgFlag) {
+ ShowRadarMarker(e->GetPosition(), GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim), ms_RadarTrace->m_Radius);
+
+ ms_RadarTrace[i].m_Radius = ms_RadarTrace[i].m_Radius - 0.1f;
+ if (ms_RadarTrace[i].m_Radius >= 1.0f)
+ ms_RadarTrace[i].m_Radius = 5.0;
+ }
+ }
+ if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) {
+ vec2d = e->GetPosition();
+ TransformRealWorldPointToRadarSpace(in, vec2d);
+ float dist = LimitRadarPoint(in);
+ int a = CalculateBlipAlpha(dist);
+ TransformRadarPointToScreenSpace(out, in);
+
+ int32 col = GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim);
+
+ if (ms_RadarTrace[i].m_IconID)
+ DrawRadarSprite(ms_RadarTrace[i].m_IconID, out.x, out.y, a);
+ else
+ ShowRadarTrace(out.x, out.y, ms_RadarTrace[i].m_wScale, ((col >> 24)), ((col >> 16) & 0xFF), ((col >> 8)), 255);
+ }
+ }
+ }
+
+ /*
+ DrawCoordBlip
+ */
+ if (ms_RadarTrace[i].m_eBlipType >= BLIP_COORD) {
+ if (ms_RadarTrace[i].m_eBlipType != BLIP_CONTACT_POINT || ms_RadarTrace[i].m_eBlipType == BLIP_CONTACT_POINT && DisplayThisBlip(i) || !CTheScripts::IsPlayerOnAMission()) {
+ if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) {
+ if (CTheScripts::DbgFlag) {
+ ShowRadarMarker(ms_RadarTrace[i].m_vecPos, GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim), ms_RadarTrace->m_Radius);
+ ms_RadarTrace[i].m_Radius = ms_RadarTrace[i].m_Radius - 0.1f;
+ if (ms_RadarTrace[i].m_Radius >= 1.0f)
+ ms_RadarTrace[i].m_Radius = 5.0f;
+ }
+ }
+
+ if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) {
+ TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[i].m_vec2DPos);
+ float dist = LimitRadarPoint(in);
+ int a = CalculateBlipAlpha(dist);
+ TransformRadarPointToScreenSpace(out, in);
+
+ int32 col = GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim);
+
+ if (ms_RadarTrace[i].m_IconID)
+ DrawRadarSprite(ms_RadarTrace[i].m_IconID, out.x, out.y, a);
+ else
+ ShowRadarTrace(out.x, out.y, ms_RadarTrace[i].m_wScale, ((col >> 24)), ((col >> 16) & 0xFF), ((col >> 8)), 255);
+ }
+ }
+ }
+ };
+ }
+ }
+}
+#endif
+
+
+#if 0
+WRAPPER void CRadar::DrawMap () { EAXJMP(0x4A4200); }
+#else
+void CRadar::DrawMap()
+{
+ if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) {
+ if (FindPlayerVehicle()) {
+ float speed = FindPlayerSpeed().Magnitude();
+ if (speed < RADAR_MIN_SPEED)
+ m_RadarRange = RADAR_MIN_RANGE;
+ else if (speed < RADAR_MAX_SPEED)
+ m_RadarRange = (speed - RADAR_MIN_SPEED)/(RADAR_MAX_SPEED-RADAR_MIN_SPEED) * (RADAR_MAX_RANGE-RADAR_MIN_RANGE) + RADAR_MIN_RANGE;
+ else
+ m_RadarRange = RADAR_MAX_RANGE;
+ }
+ else
+ m_RadarRange = RADAR_MIN_RANGE;
+
+ vec2DRadarOrigin = CVector2D(FindPlayerCentreOfWorld_NoSniperShift());
+ DrawRadarMap();
+ }
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::DrawRadarMap() { EAXJMP(0x4A6C20); }
+#else
+void CRadar::DrawRadarMap()
+{
+ // Game calculates an unused CRect here
+
+ DrawRadarMask();
+
+ // top left ist (0, 0)
+ int x = floorf((vec2DRadarOrigin.x - WORLD_MIN_X) / RADAR_TILE_SIZE);
+ int y = ceilf((RADAR_NUM_TILES - 1) - (vec2DRadarOrigin.y - WORLD_MIN_Y) / RADAR_TILE_SIZE);
+ StreamRadarSections(x, y);
+
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP);
+ RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)FALSE);
+
+ DrawRadarSection(x - 1, y - 1);
+ DrawRadarSection(x, y - 1);
+ DrawRadarSection(x + 1, y - 1);
+ DrawRadarSection(x - 1, y);
+ DrawRadarSection(x, y);
+ DrawRadarSection(x + 1, y);
+ DrawRadarSection(x - 1, y + 1);
+ DrawRadarSection(x, y + 1);
+ DrawRadarSection(x + 1, y + 1);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::DrawRadarMask() { EAXJMP(0x4A69C0); }
+#else
+void CRadar::DrawRadarMask()
+{
+ CVector2D corners[4] = {
+ CVector2D(1.0f, -1.0f),
+ CVector2D(1.0f, 1.0f),
+ CVector2D(-1.0f, 1.0f),
+ CVector2D(-1.0, -1.0f)
+ };
+
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwD3D8SetRenderState(rwRENDERSTATESTENCILFUNCTION, rwSTENCILFUNCTIONALWAYS);
+
+ CVector2D out[8];
+ CVector2D in;
+
+ // Draw the shape we want to mask out from the radar in four segments
+ for (int i = 0; i < 4; i++) {
+ // First point is always the corner itself
+ in.x = corners[i].x;
+ in.y = corners[i].y;
+ TransformRadarPointToScreenSpace(out[0], in);
+
+ // Then generate a quarter of the circle
+ for (int j = 0; j < 7; j++) {
+ in.x = corners[i].x * cos(j * (PI / 2.0f / 6.0f));
+ in.y = corners[i].y * sin(j * (PI / 2.0f / 6.0f));
+ TransformRadarPointToScreenSpace(out[j + 1], in);
+ };
+
+ CSprite2d::SetMaskVertices(8, (float *)out);
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), 8);
+ };
+
+ RwD3D8SetRenderState(rwRENDERSTATESTENCILFUNCTION, rwSTENCILFUNCTIONGREATER);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::DrawRadarSection(int32, int32) { EAXJMP(0x4A67E0); }
+#else
+void CRadar::DrawRadarSection(int32 x, int32 y)
+{
+ int i;
+ RwTexDictionary *txd;
+ CVector2D worldPoly[8];
+ CVector2D radarCorners[4];
+ CVector2D radarPoly[8];
+ CVector2D texCoords[8];
+ CVector2D screenPoly[8];
+ int numVertices;
+ RwTexture *texture = nil;
+
+ GetTextureCorners(x, y, worldPoly);
+ ClipRadarTileCoords(x, y);
+
+ assert(CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y]));
+ txd = CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])->texDict;
+ if (txd)
+ texture = GetFirstTexture(txd);
+ if (texture == nil)
+ return;
+
+ for (i = 0; i < 4; i++)
+ TransformRealWorldPointToRadarSpace(radarCorners[i], worldPoly[i]);
+
+ numVertices = ClipRadarPoly(radarPoly, radarCorners);
+
+ // FIX: can return earlier here
+// if(numVertices == 0)
+ if (numVertices < 3)
+ return;
+
+ for (i = 0; i < numVertices; i++) {
+ TransformRadarPointToRealWorldSpace(worldPoly[i], radarPoly[i]);
+ TransformRealWorldToTexCoordSpace(texCoords[i], worldPoly[i], x, y);
+ TransformRadarPointToScreenSpace(screenPoly[i], radarPoly[i]);
+ }
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(texture));
+ CSprite2d::SetVertices(numVertices, (float*)screenPoly, (float*)texCoords, CRGBA(255, 255, 255, 255));
+ // check done above now
+// if(numVertices > 2)
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), numVertices);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::DrawRadarSprite(int32 sprite, float x, float y, int32 alpha) { EAXJMP(0x4A5EF0); }
+#else
+void CRadar::DrawRadarSprite(int32 sprite, float x, float y, int32 alpha)
+{
+ RadarSprites[sprite]->Draw(CRect(x - SCREEN_SCALE_X(8.0f), y - SCREEN_SCALE_Y(8.0f), x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(8.0f)), CRGBA(255, 255, 255, alpha));
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha) { EAXJMP(0x4A5D10); }
+#else
+void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha)
+{
+ CVector curPosn[4];
+ CVector oldPosn[4];
+
+ curPosn[0].x = x - SCREEN_SCALE_X(5.6f);
+ curPosn[0].y = y + SCREEN_SCALE_Y(5.6f);
+
+ curPosn[1].x = x + SCREEN_SCALE_X(5.6f);
+ curPosn[1].y = y + SCREEN_SCALE_Y(5.6f);
+
+ curPosn[2].x = x - SCREEN_SCALE_X(5.6f);
+ curPosn[2].y = y - SCREEN_SCALE_Y(5.6f);
+
+ curPosn[3].x = x + SCREEN_SCALE_X(5.6f);
+ curPosn[3].y = y - SCREEN_SCALE_Y(5.6f);
+
+ for (uint32 i = 0; i < 4; i++) {
+ oldPosn[i] = curPosn[i];
+
+ curPosn[i].x = x + (oldPosn[i].x - x) * cosf(angle) + (oldPosn[i].y - y) * sinf(angle);
+ curPosn[i].y = y - (oldPosn[i].x - x) * sinf(angle) + (oldPosn[i].y - y) * cosf(angle);
+ }
+
+ sprite->Draw(curPosn[2].x, curPosn[2].y, curPosn[3].x, curPosn[3].y, curPosn[0].x, curPosn[0].y, curPosn[1].x, curPosn[1].y, CRGBA(255, 255, 255, alpha));
+}
+#endif
+
+#if 1
+WRAPPER int32 CRadar::GetActualBlipArray(int32) { EAXJMP(0x4A41C0); }
+#else
+int32 CRadar::GetActualBlipArray(int32 i)
+{
+ return int32();
+}
+#endif
+
+#if 1
+WRAPPER int32 CRadar::GetNewUniqueBlipIndex(int32) { EAXJMP(0x4A4180); }
+#else
+int32 CRadar::GetNewUniqueBlipIndex(int32 i)
+{
+ return int32();
+}
+#endif
+
+#if 0
+WRAPPER int32 CRadar::GetRadarTraceColour(int32 color, bool bright) { EAXJMP(0x4A5BB0); }
+#else
+int32 CRadar::GetRadarTraceColour(int32 color, bool bright)
+{
+ int32 c;
+ switch (color) {
+ case 0:
+ if (bright)
+ c = 0x712B49FF;
+ else
+ c = 0x7F0000FF;
+ break;
+ case 1:
+ if (bright)
+ c = 0x5FA06AFF;
+ else
+ c = 0x7F00FF;
+ break;
+ case 2:
+ if (bright)
+ c = 0x80A7F3FF;
+ else
+ c = 0x007FFF;
+ break;
+ case 3:
+ if (bright)
+ c = 0xE1E1E1FF;
+ else
+ c = 0x7F7F7FFF;
+ break;
+ case 4:
+ if (bright)
+ c = 0xFFFF00FF;
+ else
+ c = 0x7F7F00FF;
+ break;
+ case 5:
+ if (bright)
+ c = 0xFF00FFFF;
+ else
+ c = 0x7F007FFF;
+ break;
+ case 6:
+ if (bright)
+ c = 0xFFFFFF;
+ else
+ c = 0x7F7FFF;
+ break;
+ default:
+ c = color;
+ break;
+ };
+ return c;
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::Initialise() { EAXJMP(0x4A3EF0); }
+#else
+void CRadar::Initialise()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER float CRadar::LimitRadarPoint(CVector2D &point) { EAXJMP(0x4A4F30); }
+#else
+float CRadar::LimitRadarPoint(CVector2D &point)
+{
+ float dist, invdist;
+
+ dist = point.Magnitude();
+ if (dist > 1.0f) {
+ invdist = 1.0f / dist;
+ point.x *= invdist;
+ point.y *= invdist;
+ }
+ return dist;
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::LoadAllRadarBlips(int32) { EAXJMP(0x4A6F30); }
+#else
+void CRadar::LoadAllRadarBlips(int32)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::LoadTextures() { EAXJMP(0x4A4030); }
+#else
+void CRadar::LoadTextures()
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::RemoveRadarSections() { EAXJMP(0x4A60E0); }
+#else
+void CRadar::RemoveRadarSections()
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::RemoveMapSection(int32, int32) { EAXJMP(0x00); }
+#else
+void CRadar::RemoveMapSection(int32 x, int32 y)
+{
+ if (x >= 0 && x <= 7 && y >= 0 && y <= 7)
+ CStreaming::RemoveTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y]);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::RequestMapSection(int32, int32) { EAXJMP(0x00); }
+#else
+void CRadar::RequestMapSection(int32 x, int32 y)
+{
+ ClipRadarTileCoords(x, y);
+ CStreaming::RequestTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y], STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY);
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::SaveAllRadarBlips(int32) { EAXJMP(0x4A6E30); }
+#else
+void CRadar::SaveAllRadarBlips(int32)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::SetBlipSprite(int32, int32) { EAXJMP(0x4A5840); }
+#else
+void CRadar::SetBlipSprite(int32 i, int32 icon)
+{
+
+}
+#endif
+
+#if 1
+WRAPPER int CRadar::SetCoordBlip(int32, CVector, int32) { EAXJMP(0x4A5590); }
+#else
+int CRadar::SetCoordBlip(int32 type, CVector pos, int32 flag)
+{
+ return 0;
+}
+#endif
+
+#if 1
+WRAPPER int CRadar::SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag) { EAXJMP(0x4A5640); }
+#else
+int CRadar::SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag)
+{
+ return 0;
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::SetRadarMarkerState(int32, int32) { EAXJMP(0x4A5C60); }
+#else
+void CRadar::SetRadarMarkerState(int32 counter, int32 flag)
+{
+ CEntity *e;
+ switch (ms_RadarTrace[counter].m_eBlipType) {
+ case BLIP_CAR:
+ e = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle);
+ break;
+ case BLIP_CHAR:
+ e = CPools::GetPedPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle);
+ break;
+ case BLIP_OBJECT:
+ e = CPools::GetObjectPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle);
+ break;
+ default:
+ return;
+ }
+
+ if (e)
+ e->bHasBlip = flag;
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::ShowRadarMarker(CVector pos, int16 color, float radius) { EAXJMP(0x4A59C0); }
+#else
+void CRadar::ShowRadarMarker(CVector pos, int16 color, float radius) {
+ float f1 = radius * 0.5f;
+ float f2 = radius * 1.4f;
+ CVector p1, p2;
+
+ p1 = pos + TheCamera.GetUp()*f1;
+ p2 = pos + TheCamera.GetUp()*f2;
+ CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color);
+
+ p1 = pos - TheCamera.GetUp()*f1;
+ p2 = pos - TheCamera.GetUp()*f2;
+ CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color);
+
+ p1 = pos + TheCamera.GetRight()*f1;
+ p2 = pos + TheCamera.GetRight()*f2;
+ CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color);
+
+ p1 = pos - TheCamera.GetRight()*f1;
+ p2 = pos - TheCamera.GetRight()*f2;
+ CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha) { EAXJMP(0x4A5870); }
+#else
+void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha)
+{
+ CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size + 1.0f), y - SCREEN_SCALE_Y(size + 1.0f), SCREEN_SCALE_X(size + 1.0f) + x, SCREEN_SCALE_Y(size + 1.0f) + y), CRGBA(0, 0, 0, alpha));
+ CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size), y - SCREEN_SCALE_Y(size), SCREEN_SCALE_X(size) + x, SCREEN_SCALE_Y(size) + y), CRGBA(red, green, blue, alpha));
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::Shutdown() { EAXJMP(0x4A3F60); }
+#else
+void CRadar::Shutdown()
+{
+
+}
+#endif
+
+#if 1
+WRAPPER void CRadar::StreamRadarSections(const CVector &posn) { EAXJMP(0x4A6B60); }
+#else
+void CRadar::StreamRadarSections(const CVector &posn)
+{
+
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::StreamRadarSections(int32 x, int32 y) { EAXJMP(0x4A6100); }
+#else
+void CRadar::StreamRadarSections(int32 x, int32 y)
+{
+ for (int i = 0; i < RADAR_NUM_TILES; ++i) {
+ for (int j = 0; j < RADAR_NUM_TILES; ++j) {
+ if ((i >= x - 1 && i <= x + 1) && (j >= y - 1 && j <= y + 1))
+ RequestMapSection(i, j);
+ else
+ RemoveMapSection(i, j);
+ };
+ };
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y) { EAXJMP(0x4A5530); }
+#else
+void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y)
+{
+ out.x = in.x - (x * RADAR_TILE_SIZE + WORLD_MIN_X);
+ out.y = -(in.y - ((RADAR_NUM_TILES - y) * RADAR_TILE_SIZE + WORLD_MIN_Y));
+ out.x /= RADAR_TILE_SIZE;
+ out.y /= RADAR_TILE_SIZE;
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in) { EAXJMP(0x4A5300); }
+#else
+void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in)
+{
+ float s, c;
+
+ s = -sin(TheCamera.GetForward().Heading());
+ c = cos(TheCamera.GetForward().Heading());
+
+ if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED) {
+ s = 0.0f;
+ c = 1.0f;
+ }
+ else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) {
+ CVector forward;
+
+ if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON) {
+ forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward();
+ forward.Normalise(); // a bit useless...
+ }
+ else
+ forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind;
+
+ s = -sin(forward.Heading());
+ c = cos(forward.Heading());
+ }
+
+ out.x = s * in.y + c * in.x;
+ out.y = c * in.y - s * in.x;
+
+ out = out * m_RadarRange + vec2DRadarOrigin;
+}
+#endif
+
+// Radar space goes from -1.0 to 1.0 in x and y, top right is (1.0, 1.0)
+void CRadar::TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in)
+{
+ // FIX? scale RADAR_LEFT here somehow
+ out.x = (in.x + 1.0f)*0.5f*SCREEN_SCALE_X(RADAR_WIDTH) + RADAR_LEFT;
+ out.y = (1.0f - in.y)*0.5f*SCREEN_SCALE_Y(RADAR_HEIGHT) + SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT);
+}
+
+#if 0
+WRAPPER void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in) { EAXJMP(0x4A50D0); }
+#else
+void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in)
+{
+ float s, c;
+ if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED) {
+ s = 0.0f;
+ c = 1.0f;
+ }
+ else if (TheCamera.GetLookDirection() == LOOKING_FORWARD) {
+ s = sin(TheCamera.GetForward().Heading());
+ c = cos(TheCamera.GetForward().Heading());
+ }
+ else {
+ CVector forward;
+
+ if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON) {
+ forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward();
+ forward.Normalise(); // a bit useless...
+ }
+ else
+ forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind;
+
+ s = sin(forward.Heading());
+ c = cos(forward.Heading());
+ }
+
+ float x = (in.x - vec2DRadarOrigin.x) * (1.0f / m_RadarRange);
+ float y = (in.y - vec2DRadarOrigin.y) * (1.0f / m_RadarRange);
+
+ out.x = s * y + c * x;
+ out.y = c * y - s * x;
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::GetTextureCorners(int32 x, int32 y, CVector2D *out) { EAXJMP(0x4A61C0); };
+#else
+// Transform from section indices to world coordinates
+void CRadar::GetTextureCorners(int32 x, int32 y, CVector2D *out)
+{
+ x = x - RADAR_NUM_TILES/2;
+ y = -(y - RADAR_NUM_TILES/2);
+
+ // bottom left
+ out[0].x = RADAR_TILE_SIZE * (x);
+ out[0].y = RADAR_TILE_SIZE * (y - 1);
+
+ // bottom right
+ out[1].x = RADAR_TILE_SIZE * (x + 1);
+ out[1].y = RADAR_TILE_SIZE * (y - 1);
+
+ // top right
+ out[2].x = RADAR_TILE_SIZE * (x + 1);
+ out[2].y = RADAR_TILE_SIZE * (y);
+
+ // top left
+ out[3].x = RADAR_TILE_SIZE * (x);
+ out[3].y = RADAR_TILE_SIZE * (y);
+}
+#endif
+
+#if 0
+WRAPPER void CRadar::ClipRadarTileCoords(int32 &, int32 &) { EAXJMP(0x00); };
+#else
+void CRadar::ClipRadarTileCoords(int32 &x, int32 &y)
+{
+ if (x < 0)
+ x = 0;
+ if (x > RADAR_NUM_TILES-1)
+ x = RADAR_NUM_TILES-1;
+ if (y < 0)
+ y = 0;
+ if (y > RADAR_NUM_TILES-1)
+ y = RADAR_NUM_TILES-1;
+}
+#endif
+
+
+#if 0
+WRAPPER bool CRadar::IsPointInsideRadar(const CVector2D &) { EAXJMP(0x4A6160); }
+#else
+bool CRadar::IsPointInsideRadar(const CVector2D &point)
+{
+ if (point.x < -1.0f || point.x > 1.0f) return false;
+ if (point.y < -1.0f || point.y > 1.0f) return false;
+ return true;
+}
+#endif
+
+// clip line p1,p2 against (-1.0, 1.0) in x and y, set out to clipped point closest to p1
+#if 0
+WRAPPER int CRadar::LineRadarBoxCollision(CVector2D &, const CVector2D &, const CVector2D &) { EAXJMP(0x4A6250); }
+#else
+int CRadar::LineRadarBoxCollision(CVector2D &out, const CVector2D &p1, const CVector2D &p2)
+{
+ float d1, d2;
+ float t;
+ float x, y;
+ float shortest = 1.0f;
+ int edge = -1;
+
+ // clip against left edge, x = -1.0
+ d1 = -1.0f - p1.x;
+ d2 = -1.0f - p2.x;
+ if (d1 * d2 < 0.0f) {
+ // they are on opposite sides, get point of intersection
+ t = d1 / (d1 - d2);
+ y = (p2.y - p1.y)*t + p1.y;
+ if (y >= -1.0f && y <= 1.0f && t <= shortest) {
+ out.x = -1.0f;
+ out.y = y;
+ edge = 3;
+ shortest = t;
+ }
+ }
+
+ // clip against right edge, x = 1.0
+ d1 = p1.x - 1.0f;
+ d2 = p2.x - 1.0f;
+ if (d1 * d2 < 0.0f) {
+ // they are on opposite sides, get point of intersection
+ t = d1 / (d1 - d2);
+ y = (p2.y - p1.y)*t + p1.y;
+ if (y >= -1.0f && y <= 1.0f && t <= shortest) {
+ out.x = 1.0f;
+ out.y = y;
+ edge = 1;
+ shortest = t;
+ }
+ }
+
+ // clip against top edge, y = -1.0
+ d1 = -1.0f - p1.y;
+ d2 = -1.0f - p2.y;
+ if (d1 * d2 < 0.0f) {
+ // they are on opposite sides, get point of intersection
+ t = d1 / (d1 - d2);
+ x = (p2.x - p1.x)*t + p1.x;
+ if (x >= -1.0f && x <= 1.0f && t <= shortest) {
+ out.y = -1.0f;
+ out.x = x;
+ edge = 0;
+ shortest = t;
+ }
+ }
+
+ // clip against bottom edge, y = 1.0
+ d1 = p1.y - 1.0f;
+ d2 = p2.y - 1.0f;
+ if (d1 * d2 < 0.0f) {
+ // they are on opposite sides, get point of intersection
+ t = d1 / (d1 - d2);
+ x = (p2.x - p1.x)*t + p1.x;
+ if (x >= -1.0f && x <= 1.0f && t <= shortest) {
+ out.y = 1.0f;
+ out.x = x;
+ edge = 2;
+ shortest = t;
+ }
+ }
+
+ return edge;
+}
+#endif
+
+STARTPATCHES
+// InjectHook(0x4A3EF0, CRadar::Initialise, PATCH_JUMP);
+// InjectHook(0x4A3F60, CRadar::Shutdown, PATCH_JUMP);
+// InjectHook(0x4A4030, CRadar::LoadTextures, PATCH_JUMP);
+// InjectHook(0x4A4180, CRadar::GetNewUniqueBlipIndex, PATCH_JUMP);
+// InjectHook(0x4A41C0, CRadar::GetActualBlipArrayIndex, PATCH_JUMP);
+ InjectHook(0x4A4200, CRadar::DrawMap, PATCH_JUMP);
+ InjectHook(0x4A42F0, CRadar::DrawBlips, PATCH_JUMP);
+// InjectHook(0x4A4C70, CRadar::Draw3dMarkers, PATCH_JUMP);
+ InjectHook(0x4A4F30, CRadar::LimitRadarPoint, PATCH_JUMP);
+ InjectHook(0x4A4F90, CRadar::CalculateBlipAlpha, PATCH_JUMP);
+ InjectHook(0x4A5040, CRadar::TransformRadarPointToScreenSpace, PATCH_JUMP);
+ InjectHook(0x4A50D0, CRadar::TransformRealWorldPointToRadarSpace, PATCH_JUMP);
+ InjectHook(0x4A5300, CRadar::TransformRadarPointToRealWorldSpace, PATCH_JUMP);
+ InjectHook(0x4A5530, CRadar::TransformRealWorldToTexCoordSpace, PATCH_JUMP);
+// InjectHook(0x4A5590, CRadar::SetCoordBlip, PATCH_JUMP);
+// InjectHook(0x4A5640, CRadar::SetEntityBlip, PATCH_JUMP);
+ InjectHook(0x4A56C0, CRadar::ClearBlipForEntity, PATCH_JUMP);
+// InjectHook(0x4A5720, CRadar::ClearBlip, PATCH_JUMP);
+// InjectHook(0x4A5770, CRadar::ChangeBlipColour, PATCH_JUMP);
+// InjectHook(0x4A57A0, CRadar::ChangeBlipBrightness, PATCH_JUMP);
+// InjectHook(0x4A57E0, CRadar::ChangeBlipScale, PATCH_JUMP);
+// InjectHook(0x4A5810, CRadar::ChangeBlipDisplay, PATCH_JUMP);
+// InjectHook(0x4A5840, CRadar::SetBlipSprite, PATCH_JUMP);
+ InjectHook(0x4A5870, CRadar::ShowRadarTrace, PATCH_JUMP);
+ InjectHook(0x4A59C0, CRadar::ShowRadarMarker, PATCH_JUMP);
+ //InjectHook(0x4A5BB0, CRadar::GetRadarTraceColour, PATCH_JUMP);
+ InjectHook(0x4A5C60, CRadar::SetRadarMarkerState, PATCH_JUMP);
+ InjectHook(0x4A5D10, CRadar::DrawRotatingRadarSprite, PATCH_JUMP);
+ InjectHook(0x4A5EF0, CRadar::DrawRadarSprite, PATCH_JUMP);
+// InjectHook(0x4A60E0, CRadar::RemoveRadarSections, PATCH_JUMP);
+// InjectHook(0x4A6100, CRadar::StreamRadarSections, PATCH_JUMP);
+ InjectHook(0x4A64A0, CRadar::ClipRadarPoly, PATCH_JUMP);
+ InjectHook(0x4A67E0, CRadar::DrawRadarSection, PATCH_JUMP);
+ InjectHook(0x4A69C0, CRadar::DrawRadarMask, PATCH_JUMP);
+// InjectHook(0x4A6B60, CRadar::StreamRadarSections, PATCH_JUMP);
+ InjectHook(0x4A6C20, CRadar::DrawRadarMap, PATCH_JUMP);
+// InjectHook(0x4A6E30, CRadar::SaveAllRadarBlips, PATCH_JUMP);
+// InjectHook(0x4A6F30, CRadar::LoadAllRadarBlips, PATCH_JUMP);
+
+ InjectHook(0x4A61C0, CRadar::GetTextureCorners, PATCH_JUMP);
+ InjectHook(0x4A6160, CRadar::IsPointInsideRadar, PATCH_JUMP);
+ InjectHook(0x4A6250, CRadar::LineRadarBoxCollision, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Radar.h b/src/core/Radar.h
new file mode 100644
index 00000000..e5396a50
--- /dev/null
+++ b/src/core/Radar.h
@@ -0,0 +1,146 @@
+#pragma once
+#include "Sprite2d.h"
+
+enum eBlipType
+{
+ BLIP_NONE,
+ BLIP_CAR,
+ BLIP_CHAR,
+ BLIP_OBJECT,
+ BLIP_COORD,
+ BLIP_CONTACT_POINT
+};
+
+enum eBlipDisplay
+{
+ BLIP_DISPLAY_NEITHER = 0,
+ BLIP_DISPLAY_MARKER_ONLY = 1,
+ BLIP_DISPLAY_BLIP_ONLY = 2,
+ BLIP_DISPLAY_BOTH = 3,
+};
+
+enum eRadarSprite
+{
+ RADAR_SPRITE_NONE = 0,
+ RADAR_SPRITE_ASUKA = 1,
+ RADAR_SPRITE_BOMB = 2,
+ RADAR_SPRITE_CAT = 3,
+ RADAR_SPRITE_CENTRE = 4,
+ RADAR_SPRITE_COPCAR = 5,
+ RADAR_SPRITE_DON = 6,
+ RADAR_SPRITE_EIGHT = 7,
+ RADAR_SPRITE_EL = 8,
+ RADAR_SPRITE_ICE = 9,
+ RADAR_SPRITE_JOEY = 10,
+ RADAR_SPRITE_KENJI = 11,
+ RADAR_SPRITE_LIZ = 12,
+ RADAR_SPRITE_LUIGI = 13,
+ RADAR_SPRITE_NORTH = 14,
+ RADAR_SPRITE_RAY = 15,
+ RADAR_SPRITE_SAL = 16,
+ RADAR_SPRITE_SAVE = 17,
+ RADAR_SPRITE_SPRAY = 18,
+ RADAR_SPRITE_TONY = 19,
+ RADAR_SPRITE_WEAPON = 20,
+ RADAR_SPRITE_COUNT = 21,
+};
+
+struct CBlip
+{
+ int32 m_nColor;
+ int16 m_eBlipType; // eBlipType
+ int32 m_nEntityHandle;
+ CVector2D m_vec2DPos;
+ CVector m_vecPos;
+ int16 m_BlipIndex;
+ bool m_bDim;
+ bool m_bInUse;
+ float m_Radius;
+ int16 m_wScale;
+ int16 m_eBlipDisplay; // eBlipDisplay
+ int16 m_IconID; // eRadarSprite
+};
+static_assert(sizeof(CBlip) == 0x30, "CBlip: error");
+
+// Values for screen space
+#define RADAR_LEFT (40.0f)
+#define RADAR_BOTTOM (47.0f)
+#define RADAR_WIDTH (94.0f)
+#define RADAR_HEIGHT (76.0f)
+
+class CRadar
+{
+public:
+ static float &m_RadarRange;
+ static CBlip *ms_RadarTrace; //[NUMRADARBLIPS]
+ static CSprite2d *AsukaSprite;
+ static CSprite2d *BombSprite;
+ static CSprite2d *CatSprite;
+ static CSprite2d *CentreSprite;
+ static CSprite2d *CopcarSprite;
+ static CSprite2d *DonSprite;
+ static CSprite2d *EightSprite;
+ static CSprite2d *ElSprite;
+ static CSprite2d *IceSprite;
+ static CSprite2d *JoeySprite;
+ static CSprite2d *KenjiSprite;
+ static CSprite2d *LizSprite;
+ static CSprite2d *LuigiSprite;
+ static CSprite2d *NorthSprite;
+ static CSprite2d *RaySprite;
+ static CSprite2d *SalSprite;
+ static CSprite2d *SaveSprite;
+ static CSprite2d *SpraySprite;
+ static CSprite2d *TonySprite;
+ static CSprite2d *WeaponSprite;
+ static CSprite2d *RadarSprites[21];
+
+public:
+ static int CalculateBlipAlpha(float dist);
+ static void ChangeBlipBrightness(int32 i, int32 bright);
+ static void ChangeBlipColour(int32 i);
+ static void ChangeBlipDisplay(int32 i, int16 flag);
+ static void ChangeBlipScale(int32 i, int16 scale);
+ static void ClearBlip(int32 i);
+ static void ClearBlipForEntity(int16 type, int32 id);
+ static int ClipRadarPoly(CVector2D *out, const CVector2D *in);
+ static bool DisplayThisBlip(int32 i);
+ static void Draw3dMarkers();
+ static void DrawBlips();
+ static void DrawMap();
+ static void DrawRadarMap();
+ static void DrawRadarMask();
+ static void DrawRadarSection(int32 x, int32 y);
+ static void DrawRadarSprite(int32 sprite, float x, float y, int32 alpha);
+ static void DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha);
+ static int32 GetActualBlipArray(int32 i);
+ static int32 GetNewUniqueBlipIndex(int32 i);
+ static int32 GetRadarTraceColour(int32 color, bool bright);
+ static void Initialise();
+ static float LimitRadarPoint(CVector2D &point);
+ static void LoadAllRadarBlips(int32);
+ static void LoadTextures();
+ static void RemoveRadarSections();
+ static void RemoveMapSection(int32 x, int32 y);
+ static void RequestMapSection(int32 x, int32 y);
+ static void SaveAllRadarBlips(int32);
+ static void SetBlipSprite(int32 i, int32 icon);
+ static int SetCoordBlip(int32 type, CVector pos, int32 flag);
+ static int SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag);
+ static void SetRadarMarkerState(int32 i, int32 flag);
+ static void ShowRadarMarker(CVector pos, int16 color, float radius);
+ static void ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha);
+ static void Shutdown();
+ static void StreamRadarSections(const CVector &posn);
+ static void StreamRadarSections(int32 x, int32 y);
+ static void TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y);
+ static void TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in);
+ static void TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in);
+ static void TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in);
+
+ // no in CRadar in the game:
+ static void GetTextureCorners(int32 x, int32 y, CVector2D *out);
+ static void ClipRadarTileCoords(int32 &x, int32 &y);
+ static bool IsPointInsideRadar(const CVector2D &);
+ static int LineRadarBoxCollision(CVector2D &, const CVector2D &, const CVector2D &);
+};
diff --git a/src/core/References.cpp b/src/core/References.cpp
new file mode 100644
index 00000000..e87f0fd5
--- /dev/null
+++ b/src/core/References.cpp
@@ -0,0 +1,65 @@
+#include "common.h"
+#include "patcher.h"
+#include "World.h"
+#include "Vehicle.h"
+#include "PlayerPed.h"
+#include "Pools.h"
+#include "References.h"
+
+CReference *CReferences::aRefs = (CReference*)0x70BBE0; //[NUMREFERENCES];
+CReference *&CReferences::pEmptyList = *(CReference**)0x8F1AF8;
+
+void
+CReferences::Init(void)
+{
+ int i;
+ pEmptyList = &aRefs[0];
+ for(i = 0; i < NUMREFERENCES; i++){
+ aRefs[i].pentity = nil;
+ aRefs[i].next = &aRefs[i+1];
+ }
+ aRefs[NUMREFERENCES-1].next = nil;
+}
+
+void
+CReferences::RemoveReferencesToPlayer(void)
+{
+ if(FindPlayerVehicle())
+ FindPlayerVehicle()->ResolveReferences();
+ if(FindPlayerPed())
+ FindPlayerPed()->ResolveReferences();
+}
+
+void
+CReferences::PruneAllReferencesInWorld(void)
+{
+ int i;
+ CEntity *e;
+
+ i = CPools::GetPedPool()->GetSize();
+ while(--i >= 0){
+ e = CPools::GetPedPool()->GetSlot(i);
+ if(e)
+ e->PruneReferences();
+ }
+
+ i = CPools::GetVehiclePool()->GetSize();
+ while(--i >= 0){
+ e = CPools::GetVehiclePool()->GetSlot(i);
+ if(e)
+ e->PruneReferences();
+ }
+
+ i = CPools::GetObjectPool()->GetSize();
+ while(--i >= 0){
+ e = CPools::GetObjectPool()->GetSlot(i);
+ if(e)
+ e->PruneReferences();
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x4A7350, CReferences::Init, PATCH_JUMP);
+ InjectHook(0x4A7570, CReferences::RemoveReferencesToPlayer, PATCH_JUMP);
+ InjectHook(0x4A75A0, CReferences::PruneAllReferencesInWorld, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/References.h b/src/core/References.h
new file mode 100644
index 00000000..6476e243
--- /dev/null
+++ b/src/core/References.h
@@ -0,0 +1,20 @@
+#pragma once
+
+class CEntity;
+
+struct CReference
+{
+ CReference *next;
+ CEntity **pentity;
+};
+
+class CReferences
+{
+public:
+ static CReference *aRefs; //[NUMREFERENCES];
+ static CReference *&pEmptyList;
+
+ static void Init(void);
+ static void RemoveReferencesToPlayer(void);
+ static void PruneAllReferencesInWorld(void);
+};
diff --git a/src/core/RwClumpRead.cpp b/src/core/RwClumpRead.cpp
new file mode 100644
index 00000000..c9f027e7
--- /dev/null
+++ b/src/core/RwClumpRead.cpp
@@ -0,0 +1,230 @@
+#include "common.h"
+#include "patcher.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 = stream->Type.memory.position;
+ return true;
+}
+
+RpClump*
+RpClumpGtaStreamRead2(RwStream *stream)
+{
+ int i;
+ RwUInt32 version;
+ RpAtomic *atomic;
+ RpClump *clump;
+
+ clump = RpClumpCreate();
+ if(clump == nil)
+ return nil;
+
+ RwStreamSkip(stream, streamPosition - stream->Type.memory.position);
+
+ 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;
+}
+
+STARTPATCHES
+ InjectHook(0x526060, RpClumpGtaStreamRead1, PATCH_JUMP);
+ InjectHook(0x526180, RpClumpGtaStreamRead2, PATCH_JUMP);
+ InjectHook(0x5262D0, RpClumpGtaCancelStream, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/RwHelper.cpp b/src/core/RwHelper.cpp
new file mode 100644
index 00000000..8dade266
--- /dev/null
+++ b/src/core/RwHelper.cpp
@@ -0,0 +1,356 @@
+#define WITHD3D
+#include "common.h"
+#include "patcher.h"
+#include "Timecycle.h"
+#include "skeleton.h"
+
+void *
+RwMallocAlign(RwUInt32 size, RwUInt32 align)
+{
+ void *mem = (void *)malloc(size + align);
+
+ ASSERT(mem != nil);
+
+ void *addr = (void *)((((RwUInt32)mem) + align) & ~(align - 1));
+
+ ASSERT(addr != nil);
+
+ *(((void **)addr) - 1) = mem;
+
+ return addr;
+}
+
+void
+RwFreeAlign(void *mem)
+{
+ ASSERT(mem != nil);
+
+ void *addr = *(((void **)mem) - 1);
+
+ ASSERT(addr != nil);
+
+ free(addr);
+}
+
+void
+DefinedState(void)
+{
+ RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSWRAP);
+ RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD);
+ RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEALPHAPRIMITIVEBUFFER, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEBORDERCOLOR, (void*)RWRGBALONG(0, 0, 0, 255));
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEFOGCOLOR,
+ (void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255));
+ RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR);
+ RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE);
+
+ // D3D stuff
+ RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
+ RwD3D8SetRenderState(D3DRS_ALPHAREF, 2);
+}
+
+RwFrame*
+GetFirstFrameCallback(RwFrame *child, void *data)
+{
+ *(RwFrame**)data = child;
+ return nil;
+}
+
+RwFrame*
+GetFirstChild(RwFrame *frame)
+{
+ RwFrame *child;
+
+ child = nil;
+ RwFrameForAllChildren(frame, GetFirstFrameCallback, &child);
+ return child;
+}
+
+RwObject*
+GetFirstObjectCallback(RwObject *object, void *data)
+{
+ *(RwObject**)data = object;
+ return nil;
+}
+
+RwObject*
+GetFirstObject(RwFrame *frame)
+{
+ RwObject *obj;
+
+ obj = nil;
+ RwFrameForAllObjects(frame, GetFirstObjectCallback, &obj);
+ return obj;
+}
+
+RpAtomic*
+GetFirstAtomicCallback(RpAtomic *atm, void *data)
+{
+ *(RpAtomic**)data = atm;
+ return nil;
+}
+
+RpAtomic*
+GetFirstAtomic(RpClump *clump)
+{
+ RpAtomic *atm;
+
+ atm = nil;
+ RpClumpForAllAtomics(clump, GetFirstAtomicCallback, &atm);
+ return atm;
+}
+
+RwTexture*
+GetFirstTextureCallback(RwTexture *tex, void *data)
+{
+ *(RwTexture**)data = tex;
+ return nil;
+}
+
+RwTexture*
+GetFirstTexture(RwTexDictionary *txd)
+{
+ RwTexture *tex;
+
+ tex = nil;
+ RwTexDictionaryForAllTextures(txd, GetFirstTextureCallback, &tex);
+ return tex;
+}
+
+void
+CameraSize(RwCamera * camera, RwRect * rect,
+ RwReal viewWindow, RwReal aspectRatio)
+{
+ if (camera)
+ {
+ RwVideoMode videoMode;
+ RwRect r;
+ RwRect origSize = { 0, 0, 0, 0 }; // FIX just to make the compier happy
+ RwV2d vw;
+
+ RwEngineGetVideoModeInfo(&videoMode,
+ RwEngineGetCurrentVideoMode());
+
+ origSize.w = RwRasterGetWidth(RwCameraGetRaster(camera));
+ origSize.h = RwRasterGetHeight(RwCameraGetRaster(camera));
+
+ if (!rect)
+ {
+ if (videoMode.flags & rwVIDEOMODEEXCLUSIVE)
+ {
+ /* For full screen applications, resizing the camera just doesn't
+ * make sense, use the video mode size.
+ */
+
+ r.x = r.y = 0;
+ r.w = videoMode.width;
+ r.h = videoMode.height;
+ rect = &r;
+ }
+ else
+ {
+ /*
+ rect not specified - reuse current values
+ */
+ r.w = RwRasterGetWidth(RwCameraGetRaster(camera));
+ r.h = RwRasterGetHeight(RwCameraGetRaster(camera));
+ r.x = r.y = 0;
+ rect = &r;
+ }
+ }
+
+ if (( origSize.w != rect->w ) && ( origSize.h != rect->h ))
+ {
+ RwRaster *raster;
+ RwRaster *zRaster;
+
+ /*
+ * Destroy rasters...
+ */
+
+ raster = RwCameraGetRaster(camera);
+ if( raster )
+ {
+ RwRasterDestroy(raster);
+ }
+
+ zRaster = RwCameraGetZRaster(camera);
+ if( zRaster )
+ {
+ RwRasterDestroy(zRaster);
+ }
+
+ /*
+ * Create new rasters...
+ */
+
+ raster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA);
+ zRaster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER);
+
+ if( raster && zRaster )
+ {
+ RwCameraSetRaster(camera, raster);
+ RwCameraSetZRaster(camera, zRaster);
+ }
+ else
+ {
+ if( raster )
+ {
+ RwRasterDestroy(raster);
+ }
+
+ if( zRaster )
+ {
+ RwRasterDestroy(zRaster);
+ }
+
+ rect->x = origSize.x;
+ rect->y = origSize.y;
+ rect->w = origSize.w;
+ rect->h = origSize.h;
+
+ /*
+ * Use default values...
+ */
+ raster =
+ RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA);
+
+ zRaster =
+ RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER);
+
+ RwCameraSetRaster(camera, raster);
+ RwCameraSetZRaster(camera, zRaster);
+ }
+ }
+
+ /* Figure out the view window */
+ if (videoMode.flags & rwVIDEOMODEEXCLUSIVE)
+ {
+ /* derive ratio from aspect ratio */
+ vw.x = viewWindow;
+ vw.y = viewWindow / aspectRatio;
+ }
+ else
+ {
+ /* derive from pixel ratios */
+ if (rect->w > rect->h)
+ {
+ vw.x = viewWindow;
+ vw.y = (rect->h * viewWindow) / rect->w;
+ }
+ else
+ {
+ vw.x = (rect->w * viewWindow) / rect->h;
+ vw.y = viewWindow;
+ }
+ }
+
+ RwCameraSetViewWindow(camera, &vw);
+
+ RsGlobal.width = rect->w;
+ RsGlobal.height = rect->h;
+ }
+
+ return;
+}
+
+void
+CameraDestroy(RwCamera *camera)
+{
+ RwRaster *raster, *tmpRaster;
+ RwFrame *frame;
+
+ if (camera)
+ {
+ frame = RwCameraGetFrame(camera);
+ if (frame)
+ {
+ RwFrameDestroy(frame);
+ }
+
+ raster = RwCameraGetRaster(camera);
+ if (raster)
+ {
+ tmpRaster = RwRasterGetParent(raster);
+
+ RwRasterDestroy(raster);
+
+ if ((tmpRaster != nil) && (tmpRaster != raster))
+ {
+ RwRasterDestroy(tmpRaster);
+ }
+ }
+
+ raster = RwCameraGetZRaster(camera);
+ if (raster)
+ {
+ tmpRaster = RwRasterGetParent(raster);
+
+ RwRasterDestroy(raster);
+
+ if ((tmpRaster != nil) && (tmpRaster != raster))
+ {
+ RwRasterDestroy(tmpRaster);
+ }
+ }
+
+ RwCameraDestroy(camera);
+ }
+
+ return;
+}
+
+RwCamera *
+CameraCreate(RwInt32 width, RwInt32 height, RwBool zBuffer)
+{
+ RwCamera *camera;
+
+ camera = RwCameraCreate();
+
+ if (camera)
+ {
+ RwCameraSetFrame(camera, RwFrameCreate());
+ RwCameraSetRaster(camera,
+ RwRasterCreate(0, 0, 0, rwRASTERTYPECAMERA));
+
+ if (zBuffer)
+ {
+ RwCameraSetZRaster(camera,
+ RwRasterCreate(0, 0, 0,
+ rwRASTERTYPEZBUFFER));
+ }
+
+ /* now check that everything is valid */
+ if (RwCameraGetFrame(camera) &&
+ RwCameraGetRaster(camera) &&
+ RwRasterGetParent(RwCameraGetRaster(camera)) &&
+ (!zBuffer || (RwCameraGetZRaster(camera) &&
+ RwRasterGetParent(RwCameraGetZRaster
+ (camera)))))
+ {
+ /* everything OK */
+ return (camera);
+ }
+ }
+
+ /* if we're here then an error must have occurred so clean up */
+
+ CameraDestroy(camera);
+ return (nil);
+}
+
+STARTPATCHES
+ //InjectHook(0x526450, GetFirstObjectCallback, PATCH_JUMP);
+ InjectHook(0x526460, GetFirstObject, PATCH_JUMP);
+ InjectHook(0x527170, CameraSize, PATCH_JUMP);
+ InjectHook(0x527340, CameraDestroy, PATCH_JUMP);
+ InjectHook(0x5273B0, CameraCreate, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/RwHelper.h b/src/core/RwHelper.h
new file mode 100644
index 00000000..ef20467d
--- /dev/null
+++ b/src/core/RwHelper.h
@@ -0,0 +1,27 @@
+#pragma once
+
+void *RwMallocAlign(RwUInt32 size, RwUInt32 align);
+void RwFreeAlign(void *mem);
+
+void DefinedState(void);
+RwFrame *GetFirstChild(RwFrame *frame);
+RwObject *GetFirstObject(RwFrame *frame);
+RpAtomic *GetFirstAtomic(RpClump *clump);
+RwTexture *GetFirstTexture(RwTexDictionary *txd);
+
+RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream);
+RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream);
+RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict);
+
+bool RpClumpGtaStreamRead1(RwStream *stream);
+RpClump *RpClumpGtaStreamRead2(RwStream *stream);
+void RpClumpGtaCancelStream(void);
+
+void CameraSize(RwCamera *camera,
+ RwRect *rect,
+ RwReal viewWindow,
+ RwReal aspectRatio);
+void CameraDestroy(RwCamera *camera);
+RwCamera *CameraCreate(RwInt32 width,
+ RwInt32 height,
+ RwBool zBuffer);
diff --git a/src/core/RwMatFX.cpp b/src/core/RwMatFX.cpp
new file mode 100644
index 00000000..5fd00c54
--- /dev/null
+++ b/src/core/RwMatFX.cpp
@@ -0,0 +1,212 @@
+#define WITHD3D
+#include "common.h"
+#include "patcher.h"
+
+struct MatFXNothing { int pad[5]; int effect; };
+
+struct MatFXBump
+{
+ RwFrame *bumpFrame;
+ RwTexture *bumpedTex;
+ RwTexture *bumpTex;
+ float negBumpCoefficient;
+ int pad;
+ int effect;
+};
+
+struct MatFXEnv
+{
+ RwFrame *envFrame;
+ RwTexture *envTex;
+ float envCoeff;
+ int envFBalpha;
+ int pad;
+ int effect;
+};
+
+struct MatFXDual
+{
+ RwTexture *dualTex;
+ RwInt32 srcBlend;
+ RwInt32 dstBlend;
+};
+
+
+struct MatFX
+{
+ union {
+ MatFXNothing n;
+ MatFXBump b;
+ MatFXEnv e;
+ MatFXDual d;
+ } fx[2];
+ int effects;
+};
+
+int &MatFXMaterialDataOffset = *(int*)0x66188C;
+int &MatFXAtomicDataOffset = *(int*)0x66189C;
+
+#ifdef PS2_MATFX
+
+void
+_rpMatFXD3D8AtomicMatFXDefaultRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture)
+{
+ if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture)
+ RwD3D8SetTexture(texture, 0);
+ else
+ RwD3D8SetTexture(nil, 0);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF));
+ RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, inst->vertexAlpha != 0);
+ RwD3D8SetPixelShader(0);
+ RwD3D8SetVertexShader(inst->vertexShader);
+ RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride);
+
+ if(inst->indexBuffer){
+ RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex);
+ RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices);
+ }else
+ RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices);
+}
+
+// map [-1; -1] -> [0; 1], flip V
+static RwMatrix scalenormal = {
+ { 0.5f, 0.0f, 0.0f }, 0,
+ { 0.0f, -0.5f, 0.0f }, 0,
+ { 0.0f, 0.0f, 1.0f }, 0,
+ { 0.5f, 0.5f, 0.0f }, 0,
+
+};
+
+// flipped U for PS2
+static RwMatrix scalenormal_flipU = {
+ { -0.5f, 0.0f, 0.0f }, 0,
+ { 0.0f, -0.5f, 0.0f }, 0,
+ { 0.0f, 0.0f, 1.0f }, 0,
+ { 0.5f, 0.5f, 0.0f }, 0,
+
+};
+
+void
+ApplyEnvMapTextureMatrix(RwTexture *tex, int n, RwFrame *frame)
+{
+ RwD3D8SetTexture(tex, n);
+ RwD3D8SetTextureStageState(n, D3DRS_ALPHAREF, 2);
+ RwD3D8SetTextureStageState(n, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL);
+ if(frame){
+ RwMatrix *envframemat = RwMatrixCreate();
+ RwMatrix *tmpmat = RwMatrixCreate();
+ RwMatrix *envmat = RwMatrixCreate();
+
+ RwMatrixInvert(envframemat, RwFrameGetLTM(frame));
+ // PS2
+ // can this be simplified?
+ *tmpmat = *RwFrameGetLTM(RwCameraGetFrame((RwCamera*)RWSRCGLOBAL(curCamera)));
+ RwV3dNegate(&tmpmat->right, &tmpmat->right);
+ tmpmat->flags = 0;
+ tmpmat->pos.x = 0.0f;
+ tmpmat->pos.y = 0.0f;
+ tmpmat->pos.z = 0.0f;
+ RwMatrixMultiply(envmat, tmpmat, envframemat);
+ *tmpmat = *envmat;
+ // important because envframemat can have a translation that we don't like
+ tmpmat->pos.x = 0.0f;
+ tmpmat->pos.y = 0.0f;
+ tmpmat->pos.z = 0.0f;
+ // for some reason we flip in U as well
+ RwMatrixMultiply(envmat, tmpmat, &scalenormal_flipU);
+
+ RwD3D8SetTransform(D3DTS_TEXTURE0+n, envmat);
+
+ RwMatrixDestroy(envmat);
+ RwMatrixDestroy(tmpmat);
+ RwMatrixDestroy(envframemat);
+ }else
+ RwD3D8SetTransform(D3DTS_TEXTURE0+n, &scalenormal);
+}
+
+void
+_rpMatFXD3D8AtomicMatFXEnvRender_ps2(RxD3D8InstanceData *inst, int flags, int sel, RwTexture *texture, RwTexture *envMap)
+{
+ MatFX *matfx = *RWPLUGINOFFSET(MatFX*, inst->material, MatFXMaterialDataOffset);
+ MatFXEnv *env = &matfx->fx[sel].e;
+
+ uint8 intens = (uint8)(env->envCoeff*255.0f);
+
+ if(intens == 0 || envMap == nil){
+ if(sel == 0)
+ _rpMatFXD3D8AtomicMatFXDefaultRender(inst, flags, texture);
+ return;
+ }
+
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF));
+ if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture)
+ RwD3D8SetTexture(texture, 0);
+ else
+ RwD3D8SetTexture(nil, 0);
+ RwD3D8SetVertexShader(inst->vertexShader);
+ RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride);
+ RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex);
+ if(inst->indexBuffer)
+ RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices);
+ else
+ RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices);
+
+ // Effect pass
+
+ ApplyEnvMapTextureMatrix(envMap, 0, env->envFrame);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwUInt32 src, dst, lighting, zwrite, fog, fogcol;
+ RwRenderStateGet(rwRENDERSTATESRCBLEND, &src);
+ RwRenderStateGet(rwRENDERSTATEDESTBLEND, &dst);
+
+ // This is of course not using framebuffer alpha,
+ // but if the diffuse texture had no alpha, the result should actually be rather the same
+ if(env->envFBalpha)
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ else
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+ RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting);
+ RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, &zwrite);
+ RwD3D8GetRenderState(D3DRS_FOGENABLE, &fog);
+ RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
+ if(fog){
+ RwD3D8GetRenderState(D3DRS_FOGCOLOR, &fogcol);
+ RwD3D8SetRenderState(D3DRS_FOGCOLOR, 0);
+ }
+
+ D3DCOLOR texfactor = D3DCOLOR_RGBA(intens, intens, intens, intens);
+ RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, texfactor);
+ RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
+ RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
+ RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR);
+ // alpha unused
+ //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+ //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
+ //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR);
+
+ if(inst->indexBuffer)
+ RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices);
+ else
+ RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices);
+
+ // Reset states
+
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)src);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)dst);
+ RwD3D8SetRenderState(D3DRS_LIGHTING, lighting);
+ RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zwrite);
+ if(fog)
+ RwD3D8SetRenderState(D3DRS_FOGCOLOR, fogcol);
+ RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
+ RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
+ RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0);
+ RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
+}
+
+STARTPATCHES
+ InjectHook(0x5CF6C0, _rpMatFXD3D8AtomicMatFXEnvRender_ps2, PATCH_JUMP);
+ENDPATCHES
+
+#endif
diff --git a/src/core/RwTexRead.cpp b/src/core/RwTexRead.cpp
new file mode 100644
index 00000000..a1a7050a
--- /dev/null
+++ b/src/core/RwTexRead.cpp
@@ -0,0 +1,126 @@
+#include "common.h"
+#include "patcher.h"
+
+RwTexture*
+RwTextureGtaStreamRead(RwStream *stream)
+{
+ RwUInt32 size, version;
+ RwTexture *tex;
+
+ if(!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version))
+ return nil;
+
+ // TODO: unused timing
+
+ if(!RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, &tex, size)))
+ return nil;
+
+ return tex;
+}
+
+RwTexture*
+destroyTexture(RwTexture *texture, void *data)
+{
+ RwTextureDestroy(texture);
+ return texture;
+}
+
+RwTexDictionary*
+RwTexDictionaryGtaStreamRead(RwStream *stream)
+{
+ RwUInt32 size, version;
+ RwInt32 numTextures;
+ RwTexDictionary *texDict;
+ RwTexture *tex;
+
+ if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
+ return nil;
+ assert(size == 4);
+ if(RwStreamRead(stream, &numTextures, size) != size)
+ return nil;
+
+ texDict = RwTexDictionaryCreate();
+ if(texDict == nil)
+ return nil;
+
+ while(numTextures--){
+ tex = RwTextureGtaStreamRead(stream);
+ if(tex == nil){
+ RwTexDictionaryForAllTextures(texDict, destroyTexture, nil);
+ RwTexDictionaryDestroy(texDict);
+ return nil;
+ }
+ RwTexDictionaryAddTexture(texDict, tex);
+ }
+
+ return texDict;
+}
+
+static int32 numberTextures = -1;
+static int32 streamPosition;
+
+RwTexDictionary*
+RwTexDictionaryGtaStreamRead1(RwStream *stream)
+{
+ RwUInt32 size, version;
+ RwInt32 numTextures;
+ RwTexDictionary *texDict;
+ RwTexture *tex;
+
+ numberTextures = 0;
+ if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
+ return nil;
+ assert(size == 4);
+ if(RwStreamRead(stream, &numTextures, size) != size)
+ return nil;
+
+ texDict = RwTexDictionaryCreate();
+ if(texDict == nil)
+ return nil;
+
+ numberTextures = numTextures/2;
+
+ while(numTextures > numberTextures){
+ numTextures--;
+
+ tex = RwTextureGtaStreamRead(stream);
+ if(tex == nil){
+ RwTexDictionaryForAllTextures(texDict, destroyTexture, nil);
+ RwTexDictionaryDestroy(texDict);
+ return nil;
+ }
+ RwTexDictionaryAddTexture(texDict, tex);
+ }
+
+ numberTextures = numTextures;
+ streamPosition = stream->Type.memory.position;
+
+ return texDict;
+}
+
+RwTexDictionary*
+RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict)
+{
+ RwTexture *tex;
+
+ RwStreamSkip(stream, streamPosition - stream->Type.memory.position);
+
+ while(numberTextures--){
+ tex = RwTextureGtaStreamRead(stream);
+ if(tex == nil){
+ RwTexDictionaryForAllTextures(texDict, destroyTexture, nil);
+ RwTexDictionaryDestroy(texDict);
+ return nil;
+ }
+ RwTexDictionaryAddTexture(texDict, tex);
+ }
+
+ return texDict;
+}
+
+STARTPATCHES
+ InjectHook(0x592380, RwTextureGtaStreamRead, PATCH_JUMP);
+ InjectHook(0x5924A0, RwTexDictionaryGtaStreamRead, PATCH_JUMP);
+ InjectHook(0x592550, RwTexDictionaryGtaStreamRead1, PATCH_JUMP);
+ InjectHook(0x592650, RwTexDictionaryGtaStreamRead2, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Stats.cpp b/src/core/Stats.cpp
new file mode 100644
index 00000000..921586bb
--- /dev/null
+++ b/src/core/Stats.cpp
@@ -0,0 +1,13 @@
+#include "common.h"
+#include "Stats.h"
+
+int32 &CStats::DaysPassed = *(int32*)0x8F2BB8;
+int32 &CStats::HeadShots = *(int32*)0x8F647C;
+bool& CStats::CommercialPassed = *(bool*)0x8F4334;
+int32 &CStats::NumberKillFrenziesPassed = *(int32*)0x8E287C;
+int32 &CStats::PeopleKilledByOthers = *(int32*)0x8E2C50;
+
+void CStats::AnotherKillFrenzyPassed()
+{
+ ++NumberKillFrenziesPassed;
+} \ No newline at end of file
diff --git a/src/core/Stats.h b/src/core/Stats.h
new file mode 100644
index 00000000..a5670020
--- /dev/null
+++ b/src/core/Stats.h
@@ -0,0 +1,14 @@
+#pragma once
+
+class CStats
+{
+public:
+ static int32 &DaysPassed;
+ static int32 &HeadShots;
+ static bool& CommercialPassed;
+ static int32 &NumberKillFrenziesPassed;
+ static int32 &PeopleKilledByOthers;
+
+public:
+ static void AnotherKillFrenzyPassed();
+}; \ No newline at end of file
diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp
new file mode 100644
index 00000000..f8ab19d4
--- /dev/null
+++ b/src/core/Streaming.cpp
@@ -0,0 +1,2509 @@
+#include "common.h"
+#include "patcher.h"
+#include "Pad.h"
+#include "Hud.h"
+#include "Text.h"
+#include "Clock.h"
+#include "Renderer.h"
+#include "ModelInfo.h"
+#include "TxdStore.h"
+#include "ModelIndices.h"
+#include "Pools.h"
+#include "Directory.h"
+#include "RwHelper.h"
+#include "World.h"
+#include "Entity.h"
+#include "FileMgr.h"
+#include "FileLoader.h"
+#include "Zones.h"
+#include "ZoneCull.h"
+#include "Radar.h"
+#include "Camera.h"
+#include "Record.h"
+#include "CarCtrl.h"
+#include "Population.h"
+#include "Gangs.h"
+#include "CutsceneMgr.h"
+#include "CdStream.h"
+#include "Streaming.h"
+
+bool &CStreaming::ms_disableStreaming = *(bool*)0x95CD6E;
+bool &CStreaming::ms_bLoadingBigModel = *(bool*)0x95CDB0;
+int32 &CStreaming::ms_numModelsRequested = *(int32*)0x8E2C10;
+CStreamingInfo *CStreaming::ms_aInfoForModel = (CStreamingInfo*)0x6C7088;
+CStreamingInfo &CStreaming::ms_startLoadedList = *(CStreamingInfo*)0x942F60;
+CStreamingInfo &CStreaming::ms_endLoadedList = *(CStreamingInfo*)0x8F1AC0;
+CStreamingInfo &CStreaming::ms_startRequestedList = *(CStreamingInfo*)0x8F1B3C;
+CStreamingInfo &CStreaming::ms_endRequestedList = *(CStreamingInfo*)0x940738;
+int32 &CStreaming::ms_oldSectorX = *(int32*)0x8F2C84;
+int32 &CStreaming::ms_oldSectorY = *(int32*)0x8F2C88;
+int32 &CStreaming::ms_streamingBufferSize = *(int32*)0x942FB0;
+int8 **CStreaming::ms_pStreamingBuffer = (int8**)0x87F818;
+int32 &CStreaming::ms_memoryUsed = *(int32*)0x940568;
+CStreamingChannel *CStreaming::ms_channel = (CStreamingChannel*)0x727EE0;
+int32 &CStreaming::ms_channelError = *(int32*)0x880DB8;
+int32 &CStreaming::ms_numVehiclesLoaded = *(int32*)0x8F2C80;
+int32 *CStreaming::ms_vehiclesLoaded = (int32*)0x773560;
+int32 &CStreaming::ms_lastVehicleDeleted = *(int32*)0x95CBF8;
+CDirectory *&CStreaming::ms_pExtraObjectsDir = *(CDirectory**)0x95CB90;
+int32 &CStreaming::ms_numPriorityRequests = *(int32*)0x8F31C4;
+bool &CStreaming::ms_hasLoadedLODs = *(bool*)0x95CD47;
+int32 &CStreaming::ms_currentPedGrp = *(int32*)0x8F2BBC;
+int32 CStreaming::ms_currentPedLoading;
+int32 CStreaming::ms_lastCullZone;
+uint16 &CStreaming::ms_loadedGangs = *(uint16*)0x95CC60;
+uint16 &CStreaming::ms_loadedGangCars = *(uint16*)0x95CC2E;
+int32 *CStreaming::ms_imageOffsets = (int32*)0x6E60A0;
+int32 &CStreaming::ms_lastImageRead = *(int32*)0x880E2C;
+int32 &CStreaming::ms_imageSize = *(int32*)0x8F1A34;
+uint32 &CStreaming::ms_memoryAvailable = *(uint32*)0x880F8C;
+
+int32 &desiredNumVehiclesLoaded = *(int32*)0x5EC194;
+
+CEntity *&pIslandLODindustEntity = *(CEntity**)0x6212DC;
+CEntity *&pIslandLODcomIndEntity = *(CEntity**)0x6212E0;
+CEntity *&pIslandLODcomSubEntity = *(CEntity**)0x6212E4;
+CEntity *&pIslandLODsubIndEntity = *(CEntity**)0x6212E8;
+CEntity *&pIslandLODsubComEntity = *(CEntity**)0x6212EC;
+int32 &islandLODindust = *(int32*)0x6212C8;
+int32 &islandLODcomInd = *(int32*)0x6212CC;
+int32 &islandLODcomSub = *(int32*)0x6212D0;
+int32 &islandLODsubInd = *(int32*)0x6212D4;
+int32 &islandLODsubCom = *(int32*)0x6212D8;
+
+bool
+CStreamingInfo::GetCdPosnAndSize(uint32 &posn, uint32 &size)
+{
+ if(m_size == 0)
+ return false;
+ posn = m_position;
+ size = m_size;
+ return true;
+}
+
+void
+CStreamingInfo::SetCdPosnAndSize(uint32 posn, uint32 size)
+{
+ m_position = posn;
+ m_size = size;
+}
+
+void
+CStreamingInfo::AddToList(CStreamingInfo *link)
+{
+ // Insert this after link
+ m_next = link->m_next;
+ m_prev = link;
+ link->m_next = this;
+ m_next->m_prev = this;
+}
+
+void
+CStreamingInfo::RemoveFromList(void)
+{
+ m_next->m_prev = m_prev;
+ m_prev->m_next = m_next;
+ m_next = nil;
+ m_prev = nil;
+}
+
+void
+CStreaming::Init(void)
+{
+ int i;
+
+ for(i = 0; i < NUMSTREAMINFO; i++){
+ ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED;
+ ms_aInfoForModel[i].m_next = nil;
+ ms_aInfoForModel[i].m_prev = nil;
+ ms_aInfoForModel[i].m_nextID = -1;
+ ms_aInfoForModel[i].m_size = 0;
+ ms_aInfoForModel[i].m_position = 0;
+ }
+
+ ms_channelError = -1;
+
+ // init lists
+
+ ms_startLoadedList.m_next = &ms_endLoadedList;
+ ms_startLoadedList.m_prev = nil;
+ ms_endLoadedList.m_prev = &ms_startLoadedList;
+ ms_endLoadedList.m_next = nil;
+
+ ms_startRequestedList.m_next = &ms_endRequestedList;
+ ms_startRequestedList.m_prev = nil;
+ ms_endRequestedList.m_prev = &ms_startRequestedList;
+ ms_endRequestedList.m_next = nil;
+
+ // init misc
+
+ ms_oldSectorX = 0;
+ ms_oldSectorY = 0;
+ ms_streamingBufferSize = 0;
+ ms_disableStreaming = false;
+ ms_memoryUsed = 0;
+ ms_bLoadingBigModel = false;
+
+ // init channels
+
+ ms_channel[0].state = CHANNELSTATE_IDLE;
+ ms_channel[1].state = CHANNELSTATE_IDLE;
+ for(i = 0; i < 4; i++){
+ ms_channel[0].streamIds[i] = -1;
+ ms_channel[0].offsets[i] = -1;
+ ms_channel[1].streamIds[i] = -1;
+ ms_channel[1].offsets[i] = -1;
+ }
+
+ // init stream info, mark things that are already loaded
+
+ for(i = 0; i < MODELINFOSIZE; i++){
+ CBaseModelInfo *mi = CModelInfo::GetModelInfo(i);
+ if(mi && mi->GetRwObject()){
+ ms_aInfoForModel[i].m_loadState = STREAMSTATE_LOADED;
+ ms_aInfoForModel[i].m_flags = STREAMFLAGS_DONT_REMOVE;
+ if(mi->IsSimple())
+ ((CSimpleModelInfo*)mi)->m_alpha = 255;
+ }
+ }
+
+ for(i = 0; i < TXDSTORESIZE; i++)
+ if(CTxdStore::GetSlot(i) && CTxdStore::GetSlot(i)->texDict)
+ ms_aInfoForModel[i + STREAM_OFFSET_TXD].m_loadState = STREAMSTATE_LOADED;
+
+
+ for(i = 0; i < MAXVEHICLESLOADED; i++)
+ ms_vehiclesLoaded[i] = -1;
+ ms_numVehiclesLoaded = 0;
+
+ ms_pExtraObjectsDir = new CDirectory(EXTRADIRSIZE);
+ ms_numPriorityRequests = 0;
+ ms_hasLoadedLODs = true;
+ ms_currentPedGrp = -1;
+ ms_lastCullZone = -1; // unused because RemoveModelsNotVisibleFromCullzone is gone
+ ms_loadedGangs = 0;
+ ms_currentPedLoading = 8; // unused, whatever it is
+
+ LoadCdDirectory();
+
+ // allocate streaming buffers
+ if(ms_streamingBufferSize & 1) ms_streamingBufferSize++;
+ ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE);
+ ms_streamingBufferSize /= 2;
+ ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE;
+ debug("Streaming buffer size is %d sectors", ms_streamingBufferSize);
+
+ // PC only, figure out how much memory we got
+#ifdef GTA_PC
+#define MB (1024*1024)
+ extern DWORD &_dwMemAvailPhys;
+ ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2;
+ if(ms_memoryAvailable < 50*MB)
+ ms_memoryAvailable = 50*MB;
+ desiredNumVehiclesLoaded = (ms_memoryAvailable/MB - 50)/3 + 12;
+ if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED)
+ desiredNumVehiclesLoaded = MAXVEHICLESLOADED;
+ debug("Memory allocated to Streaming is %dMB", ms_memoryAvailable/MB);
+#undef MB
+#endif
+
+ // find island LODs
+
+ pIslandLODindustEntity = nil;
+ pIslandLODcomIndEntity = nil;
+ pIslandLODcomSubEntity = nil;
+ pIslandLODsubIndEntity = nil;
+ pIslandLODsubComEntity = nil;
+ islandLODindust = -1;
+ islandLODcomInd = -1;
+ islandLODcomSub = -1;
+ islandLODsubInd = -1;
+ islandLODsubCom = -1;
+ CModelInfo::GetModelInfo("IslandLODInd", &islandLODindust);
+ CModelInfo::GetModelInfo("IslandLODcomIND", &islandLODcomInd);
+ CModelInfo::GetModelInfo("IslandLODcomSUB", &islandLODcomSub);
+ CModelInfo::GetModelInfo("IslandLODsubIND", &islandLODsubInd);
+ CModelInfo::GetModelInfo("IslandLODsubCOM", &islandLODsubCom);
+
+ for(i = 0; i < CPools::GetBuildingPool()->GetSize(); i++){
+ CBuilding *building = CPools::GetBuildingPool()->GetSlot(i);
+ if(building == nil)
+ continue;
+ if(building->GetModelIndex() == islandLODindust) pIslandLODindustEntity = building;
+ if(building->GetModelIndex() == islandLODcomInd) pIslandLODcomIndEntity = building;
+ if(building->GetModelIndex() == islandLODcomSub) pIslandLODcomSubEntity = building;
+ if(building->GetModelIndex() == islandLODsubInd) pIslandLODsubIndEntity = building;
+ if(building->GetModelIndex() == islandLODsubCom) pIslandLODsubComEntity = building;
+ }
+}
+
+void
+CStreaming::Shutdown(void)
+{
+ RwFreeAlign(ms_pStreamingBuffer[0]);
+ ms_streamingBufferSize = 0;
+ if(ms_pExtraObjectsDir)
+ delete ms_pExtraObjectsDir;
+}
+
+void
+CStreaming::Update(void)
+{
+ CEntity *train;
+ CStreamingInfo *si, *prev;
+ bool requestedSubway = false;
+
+ UpdateMemoryUsed();
+
+ if(ms_channelError != -1){
+ RetryLoadFile(ms_channelError);
+ return;
+ }
+
+ if(CTimer::GetIsPaused())
+ return;
+
+ train = FindPlayerTrain();
+ if(train && train->GetPosition().z < 0.0f){
+ RequestSubway();
+ requestedSubway = true;
+ }else if(!ms_disableStreaming)
+ AddModelsToRequestList(TheCamera.GetPosition());
+
+ DeleteFarAwayRwObjects(TheCamera.GetPosition());
+
+ if(!ms_disableStreaming &&
+ !CCutsceneMgr::IsRunning() &&
+ !requestedSubway &&
+ !CGame::playingIntro &&
+ ms_numModelsRequested < 5 &&
+ !CRenderer::m_loadingPriority){
+ StreamVehiclesAndPeds();
+ StreamZoneModels(FindPlayerCoors());
+ }
+
+ LoadRequestedModels();
+
+ for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){
+ prev = si->m_prev;
+ if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0)
+ RemoveModel(si - ms_aInfoForModel);
+ }
+}
+
+void
+CStreaming::LoadCdDirectory(void)
+{
+ char dirname[132];
+ int i;
+
+#ifdef GTA_PC
+ ms_imageOffsets[0] = 0;
+ ms_imageOffsets[1] = -1;
+ ms_imageOffsets[2] = -1;
+ ms_imageOffsets[3] = -1;
+ ms_imageOffsets[4] = -1;
+ ms_imageOffsets[5] = -1;
+ ms_imageOffsets[6] = -1;
+ ms_imageOffsets[7] = -1;
+ ms_imageOffsets[8] = -1;
+ ms_imageOffsets[9] = -1;
+ ms_imageOffsets[10] = -1;
+ ms_imageOffsets[11] = -1;
+ ms_imageSize = GetGTA3ImgSize();
+ // PS2 uses CFileMgr::GetCdFile on all IMG files to fill the array
+#endif
+
+ i = CdStreamGetNumImages();
+ while(i-- >= 1){
+ strcpy(dirname, CdStreamGetImageName(i));
+ strncpy(strrchr(dirname, '.') + 1, "DIR", 3);
+ LoadCdDirectory(dirname, i);
+ }
+
+ ms_lastImageRead = 0;
+ ms_imageSize /= CDSTREAM_SECTOR_SIZE;
+}
+
+void
+CStreaming::LoadCdDirectory(const char *dirname, int n)
+{
+ int fd, lastID, imgSelector;
+ int modelId, txdId;
+ uint32 posn, size;
+ CDirectory::DirectoryInfo direntry;
+ char *dot;
+
+ lastID = -1;
+ fd = CFileMgr::OpenFile(dirname, "rb");
+ assert(fd > 0);
+
+ imgSelector = n<<24;
+ assert(sizeof(direntry) == 32);
+ while(CFileMgr::Read(fd, (char*)&direntry, sizeof(direntry))){
+ dot = strchr(direntry.name, '.');
+ if(dot) *dot = '\0';
+ if(direntry.size > (uint32)ms_streamingBufferSize)
+ ms_streamingBufferSize = direntry.size;
+
+ if(strcmp(dot+1, "DFF") == 0 || strcmp(dot+1, "dff") == 0){
+ if(CModelInfo::GetModelInfo(direntry.name, &modelId)){
+ if(ms_aInfoForModel[modelId].GetCdPosnAndSize(posn, size)){
+ debug("%s appears more than once in %s\n", direntry.name, dirname);
+ lastID = -1;
+ }else{
+ direntry.offset |= imgSelector;
+ ms_aInfoForModel[modelId].SetCdPosnAndSize(direntry.offset, direntry.size);
+ if(lastID != -1)
+ ms_aInfoForModel[lastID].m_nextID = modelId;
+ lastID = modelId;
+ }
+ }else{
+ // BUG: doesn't remember which cdimage this was in
+ ms_pExtraObjectsDir->AddItem(direntry);
+ lastID = -1;
+ }
+ }else if(strcmp(dot+1, "TXD") == 0 || strcmp(dot+1, "txd") == 0){
+ txdId = CTxdStore::FindTxdSlot(direntry.name);
+ if(txdId == -1)
+ txdId = CTxdStore::AddTxdSlot(direntry.name);
+ if(ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].GetCdPosnAndSize(posn, size)){
+ debug("%s appears more than once in %s\n", direntry.name, dirname);
+ lastID = -1;
+ }else{
+ direntry.offset |= imgSelector;
+ ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].SetCdPosnAndSize(direntry.offset, direntry.size);
+ if(lastID != -1)
+ ms_aInfoForModel[lastID].m_nextID = txdId + STREAM_OFFSET_TXD;
+ lastID = txdId + STREAM_OFFSET_TXD;
+ }
+ }else
+ lastID = -1;
+ }
+
+ CFileMgr::CloseFile(fd);
+}
+
+bool
+CStreaming::ConvertBufferToObject(int8 *buf, int32 streamId)
+{
+ RwMemory mem;
+ RwStream *stream;
+ int cdsize;
+ uint32 startTime, endTime, timeDiff;
+ CBaseModelInfo *mi;
+ bool success;
+
+ startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond();
+
+ cdsize = ms_aInfoForModel[streamId].GetCdSize();
+ mem.start = (uint8*)buf;
+ mem.length = cdsize * CDSTREAM_SECTOR_SIZE;
+ stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem);
+
+ if(streamId < STREAM_OFFSET_TXD){
+ // Model
+ mi = CModelInfo::GetModelInfo(streamId);
+
+ // Txd has to be loaded
+ if(CTxdStore::GetSlot(mi->GetTxdSlot())->texDict == nil){
+ debug("failed to load %s because TXD %s is not in memory\n", mi->GetName(), CTxdStore::GetTxdName(mi->GetTxdSlot()));
+ RemoveModel(streamId);
+ RemoveTxd(mi->GetTxdSlot());
+ ReRequestModel(streamId);
+ RwStreamClose(stream, &mem);
+ return false;
+ }
+
+ // Set Txd to use
+ CTxdStore::AddRef(mi->GetTxdSlot());
+ CTxdStore::SetCurrentTxd(mi->GetTxdSlot());
+
+ if(mi->IsSimple()){
+ success = CFileLoader::LoadAtomicFile(stream, streamId);
+ }else if(mi->m_type == MITYPE_VEHICLE){
+ // load vehicles in two parts
+ CModelInfo::GetModelInfo(streamId)->AddRef();
+ success = CFileLoader::StartLoadClumpFile(stream, streamId);
+ if(success)
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED;
+ }else{
+ success = CFileLoader::LoadClumpFile(stream, streamId);
+ }
+ UpdateMemoryUsed();
+
+ // Txd no longer needed unless we only read part of the file
+ if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED)
+ CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot());
+
+ if(!success){
+ debug("Failed to load %s\n", CModelInfo::GetModelInfo(streamId)->GetName());
+ RemoveModel(streamId);
+ ReRequestModel(streamId);
+ RwStreamClose(stream, &mem);
+ return false;
+ }
+ }else{
+ // Txd
+ assert(streamId < NUMSTREAMINFO);
+ if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY) == 0 &&
+ !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){
+ RemoveModel(streamId);
+ RwStreamClose(stream, &mem);
+ return false;
+ }
+
+ if(ms_bLoadingBigModel || cdsize > 200){
+ success = CTxdStore::StartLoadTxd(streamId - STREAM_OFFSET_TXD, stream);
+ if(success)
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED;
+ }else
+ success = CTxdStore::LoadTxd(streamId - STREAM_OFFSET_TXD, stream);
+ UpdateMemoryUsed();
+
+ if(!success){
+ debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD));
+ RemoveModel(streamId);
+ ReRequestModel(streamId);
+ RwStreamClose(stream, &mem);
+ return false;
+ }
+ }
+
+ RwStreamClose(stream, &mem);
+
+ // We shouldn't even end up here unless load was successful
+ if(!success){
+ ReRequestModel(streamId);
+ if(streamId < STREAM_OFFSET_TXD)
+ debug("Failed to load %s.dff\n", mi->GetName());
+ else
+ debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD));
+ return false;
+ }
+
+ if(streamId < STREAM_OFFSET_TXD){
+ // Model
+ // Vehicles and Peds not in loaded list
+ if(mi->m_type != MITYPE_VEHICLE && mi->m_type != MITYPE_PED){
+ CSimpleModelInfo *smi = (CSimpleModelInfo*)mi;
+
+ // Set fading for some objects
+ if(mi->IsSimple() && !smi->m_isBigBuilding){
+ if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOFADE)
+ smi->m_alpha = 255;
+ else
+ smi->m_alpha = 0;
+ }
+
+ if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0)
+ ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList);
+ }
+ }else{
+ // Txd
+ if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0)
+ ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList);
+ }
+
+ // Mark objects as loaded
+ if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED;
+ ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE;
+ }
+
+ endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond();
+ timeDiff = endTime - startTime;
+ if(timeDiff > 5){
+ if(streamId < STREAM_OFFSET_TXD)
+ debug("model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetName(), timeDiff);
+ else
+ debug("txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff);
+ }
+
+ return true;
+}
+
+
+bool
+CStreaming::FinishLoadingLargeFile(int8 *buf, int32 streamId)
+{
+ RwMemory mem;
+ RwStream *stream;
+ uint32 startTime, endTime, timeDiff;
+ CBaseModelInfo *mi;
+ bool success;
+
+ startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond();
+
+ if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){
+ if(streamId < STREAM_OFFSET_TXD)
+ CModelInfo::GetModelInfo(streamId)->RemoveRef();
+ return false;
+ }
+
+ mem.start = (uint8*)buf;
+ mem.length = ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE;
+ stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem);
+
+ if(streamId < STREAM_OFFSET_TXD){
+ // Model
+ mi = CModelInfo::GetModelInfo(streamId);
+ CTxdStore::SetCurrentTxd(mi->GetTxdSlot());
+ success = CFileLoader::FinishLoadClumpFile(stream, streamId);
+ if(success)
+ success = AddToLoadedVehiclesList(streamId);
+ mi->RemoveRef();
+ CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot());
+ }else{
+ // Txd
+ CTxdStore::AddRef(streamId - STREAM_OFFSET_TXD);
+ success = CTxdStore::FinishLoadTxd(streamId - STREAM_OFFSET_TXD, stream);
+ CTxdStore::RemoveRefWithoutDelete(streamId - STREAM_OFFSET_TXD);
+ }
+
+ RwStreamClose(stream, &mem);
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED;
+ ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE;
+
+ if(!success){
+ RemoveModel(streamId);
+ ReRequestModel(streamId);
+ UpdateMemoryUsed();
+ return false;
+ }
+
+ UpdateMemoryUsed();
+
+ endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond();
+ timeDiff = endTime - startTime;
+ if(timeDiff > 5){
+ if(streamId < STREAM_OFFSET_TXD)
+ debug("finish model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetName(), timeDiff);
+ else
+ debug("finish txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff);
+ }
+
+ return true;
+}
+
+void
+CStreaming::RequestModel(int32 id, int32 flags)
+{
+ CSimpleModelInfo *mi;
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE){
+ // updgrade to priority
+ if(flags & STREAMFLAGS_PRIORITY && !ms_aInfoForModel[id].IsPriority()){
+ ms_numPriorityRequests++;
+ ms_aInfoForModel[id].m_flags |= STREAMFLAGS_PRIORITY;
+ }
+ }else if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_NOTLOADED){
+ flags &= ~STREAMFLAGS_PRIORITY;
+ }
+ ms_aInfoForModel[id].m_flags |= flags;
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){
+ // Already loaded, only check changed flags
+
+ if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOFADE && id < STREAM_OFFSET_TXD){
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id);
+ if(mi->IsSimple())
+ mi->m_alpha = 255;
+ }
+
+ // reinsert into list
+ if(ms_aInfoForModel[id].m_next){
+ ms_aInfoForModel[id].RemoveFromList();
+ if((ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0)
+ ms_aInfoForModel[id].AddToList(&ms_startLoadedList);
+ }
+ }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED ||
+ ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ // how can this be true again?
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED){
+ if(id < STREAM_OFFSET_TXD)
+ RequestTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot(), flags);
+ ms_aInfoForModel[id].AddToList(&ms_startRequestedList);
+ ms_numModelsRequested++;
+ if(flags & STREAMFLAGS_PRIORITY)
+ ms_numPriorityRequests++;
+ }
+
+ ms_aInfoForModel[id].m_loadState = STREAMSTATE_INQUEUE;
+ ms_aInfoForModel[id].m_flags = flags;
+ }
+}
+
+void
+CStreaming::RequestSubway(void)
+{
+ RequestModel(MI_SUBWAY1, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY2, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY3, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY4, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY5, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY6, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY7, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY8, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY9, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY10, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY11, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY12, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY13, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY14, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY15, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY16, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY17, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBWAY18, STREAMFLAGS_NOFADE);
+
+ switch(CGame::currLevel){
+ case LEVEL_INDUSTRIAL:
+ RequestModel(MI_SUBPLATFORM_IND, STREAMFLAGS_NOFADE);
+ break;
+ case LEVEL_COMMERCIAL:
+ if(FindPlayerTrain()->GetPosition().y < -700.0f){
+ RequestModel(MI_SUBPLATFORM_COMS, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBPLATFORM_COMS2, STREAMFLAGS_NOFADE);
+ }else{
+ RequestModel(MI_SUBPLATFORM_COMN, STREAMFLAGS_NOFADE);
+ }
+ break;
+ case LEVEL_SUBURBAN:
+ RequestModel(MI_SUBPLATFORM_SUB, STREAMFLAGS_NOFADE);
+ RequestModel(MI_SUBPLATFORM_SUB2, STREAMFLAGS_NOFADE);
+ break;
+ }
+}
+
+void
+CStreaming::RequestBigBuildings(eLevelName level)
+{
+ int i, n;
+ CBuilding *b;
+
+ n = CPools::GetBuildingPool()->GetSize();
+ for(i = 0; i < n; i++){
+ b = CPools::GetBuildingPool()->GetSlot(i);
+ if(b && b->bIsBIGBuilding && b->m_level == level)
+ RequestModel(b->GetModelIndex(), STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ }
+ RequestIslands(level);
+ ms_hasLoadedLODs = false;
+}
+
+void
+CStreaming::RequestIslands(eLevelName level)
+{
+ switch(level){
+ case LEVEL_INDUSTRIAL:
+ RequestModel(islandLODcomInd, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ RequestModel(islandLODsubInd, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ break;
+ case LEVEL_COMMERCIAL:
+ RequestModel(islandLODindust, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ RequestModel(islandLODsubCom, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ break;
+ case LEVEL_SUBURBAN:
+ RequestModel(islandLODindust, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ RequestModel(islandLODcomSub, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY);
+ break;
+ }
+}
+
+void
+CStreaming::RequestSpecialModel(int32 modelId, const char *modelName, int32 flags)
+{
+ CBaseModelInfo *mi;
+ int txdId;
+ char oldName[48];
+ uint32 pos, size;
+
+ mi = CModelInfo::GetModelInfo(modelId);
+ if(strcmp(mi->GetName(), modelName) == 0){
+ // Already have the correct name, just request it
+ RequestModel(modelId, flags);
+ return;
+ }
+
+ strcpy(oldName, mi->GetName());
+ mi->SetName(modelName);
+
+ // What exactly is going on here?
+ if(CModelInfo::GetModelInfo(oldName, nil)){
+ txdId = CTxdStore::FindTxdSlot(oldName);
+ if(txdId != -1 && CTxdStore::GetSlot(txdId)->texDict){
+ CTxdStore::AddRef(txdId);
+ RemoveModel(modelId);
+ CTxdStore::RemoveRefWithoutDelete(txdId);
+ }else
+ RemoveModel(modelId);
+ }else
+ RemoveModel(modelId);
+
+ ms_pExtraObjectsDir->FindItem(modelName, pos, size);
+ mi->ClearTexDictionary();
+ if(CTxdStore::FindTxdSlot(modelName) == -1)
+ mi->SetTexDictionary("generic");
+ else
+ mi->SetTexDictionary(modelName);
+ ms_aInfoForModel[modelId].SetCdPosnAndSize(pos, size);
+ RequestModel(modelId, flags);
+}
+
+void
+CStreaming::RequestSpecialChar(int32 charId, const char *modelName, int32 flags)
+{
+ RequestSpecialModel(charId + MI_SPECIAL01, modelName, flags);
+}
+
+bool
+CStreaming::HasSpecialCharLoaded(int32 id)
+{
+ return HasModelLoaded(id + MI_SPECIAL01);
+}
+
+void
+CStreaming::SetMissionDoesntRequireSpecialChar(int32 id)
+{
+ return SetMissionDoesntRequireModel(id + MI_SPECIAL01);
+}
+
+void
+CStreaming::DecrementRef(int32 id)
+{
+ ms_numModelsRequested--;
+ if(ms_aInfoForModel[id].IsPriority()){
+ ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_PRIORITY;
+ ms_numPriorityRequests--;
+ }
+}
+
+void
+CStreaming::RemoveModel(int32 id)
+{
+ int i;
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED)
+ return;
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){
+ if(id < STREAM_OFFSET_TXD)
+ CModelInfo::GetModelInfo(id)->DeleteRwObject();
+ else
+ CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD);
+ ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE;
+ }
+
+ if(ms_aInfoForModel[id].m_next){
+ // Remove from list, model is neither loaded nor requested
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE)
+ DecrementRef(id);
+ ms_aInfoForModel[id].RemoveFromList();
+ }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_READING){
+ for(i = 0; i < 4; i++){
+ if(ms_channel[0].streamIds[i] == id)
+ ms_channel[0].streamIds[i] = -1;
+ if(ms_channel[1].streamIds[i] == id)
+ ms_channel[1].streamIds[i] = -1;
+ }
+ }
+
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){
+ if(id < STREAM_OFFSET_TXD)
+ RpClumpGtaCancelStream();
+ else
+ CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD);
+ }
+
+ ms_aInfoForModel[id].m_loadState = STREAMSTATE_NOTLOADED;
+}
+
+void
+CStreaming::RemoveUnusedBuildings(eLevelName level)
+{
+ if(level != LEVEL_INDUSTRIAL)
+ RemoveBuildings(LEVEL_INDUSTRIAL);
+ if(level != LEVEL_COMMERCIAL)
+ RemoveBuildings(LEVEL_COMMERCIAL);
+ if(level != LEVEL_SUBURBAN)
+ RemoveBuildings(LEVEL_SUBURBAN);
+}
+
+void
+CStreaming::RemoveBuildings(eLevelName level)
+{
+ int i, n;
+ CEntity *e;
+ CBaseModelInfo *mi;
+
+ n = CPools::GetBuildingPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetBuildingPool()->GetSlot(i);
+ if(e && e->m_level == level){
+ mi = CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(!e->bImBeingRendered){
+ e->DeleteRwObject();
+ if(mi->m_refCount == 0)
+ RemoveModel(e->GetModelIndex());
+ }
+ }
+ }
+
+ n = CPools::GetTreadablePool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetTreadablePool()->GetSlot(i);
+ if(e && e->m_level == level){
+ mi = CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(!e->bImBeingRendered){
+ e->DeleteRwObject();
+ if(mi->m_refCount == 0)
+ RemoveModel(e->GetModelIndex());
+ }
+ }
+ }
+
+ n = CPools::GetObjectPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetObjectPool()->GetSlot(i);
+ if(e && e->m_level == level){
+ mi = CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(!e->bImBeingRendered && ((CObject*)e)->ObjectCreatedBy == GAME_OBJECT){
+ e->DeleteRwObject();
+ if(mi->m_refCount == 0)
+ RemoveModel(e->GetModelIndex());
+ }
+ }
+ }
+
+ n = CPools::GetDummyPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetDummyPool()->GetSlot(i);
+ if(e && e->m_level == level){
+ mi = CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(!e->bImBeingRendered){
+ e->DeleteRwObject();
+ if(mi->m_refCount == 0)
+ RemoveModel(e->GetModelIndex());
+ }
+ }
+ }
+}
+
+void
+CStreaming::RemoveUnusedBigBuildings(eLevelName level)
+{
+ if(level != LEVEL_INDUSTRIAL)
+ RemoveBigBuildings(LEVEL_INDUSTRIAL);
+ if(level != LEVEL_COMMERCIAL)
+ RemoveBigBuildings(LEVEL_COMMERCIAL);
+ if(level != LEVEL_SUBURBAN)
+ RemoveBigBuildings(LEVEL_SUBURBAN);
+ RemoveIslandsNotUsed(level);
+}
+
+void
+DeleteIsland(CEntity *island)
+{
+ if(island == nil)
+ return;
+ if(island->bImBeingRendered)
+ debug("Didn't delete island because it was being rendered\n");
+ else{
+ island->DeleteRwObject();
+ CStreaming::RemoveModel(island->GetModelIndex());
+ }
+}
+
+void
+CStreaming::RemoveIslandsNotUsed(eLevelName level)
+{
+ switch(level){
+ case LEVEL_INDUSTRIAL:
+ DeleteIsland(pIslandLODindustEntity);
+ DeleteIsland(pIslandLODcomSubEntity);
+ DeleteIsland(pIslandLODsubComEntity);
+ break;
+ case LEVEL_COMMERCIAL:
+ DeleteIsland(pIslandLODcomIndEntity);
+ DeleteIsland(pIslandLODcomSubEntity);
+ DeleteIsland(pIslandLODsubIndEntity);
+ break;
+ case LEVEL_SUBURBAN:
+ DeleteIsland(pIslandLODsubIndEntity);
+ DeleteIsland(pIslandLODsubComEntity);
+ DeleteIsland(pIslandLODcomIndEntity);
+ break;
+ default:
+ DeleteIsland(pIslandLODindustEntity);
+ DeleteIsland(pIslandLODcomIndEntity);
+ DeleteIsland(pIslandLODcomSubEntity);
+ DeleteIsland(pIslandLODsubIndEntity);
+ DeleteIsland(pIslandLODsubComEntity);
+ break;
+ }
+}
+
+void
+CStreaming::RemoveBigBuildings(eLevelName level)
+{
+ int i, n;
+ CEntity *e;
+ CBaseModelInfo *mi;
+
+ n = CPools::GetBuildingPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetBuildingPool()->GetSlot(i);
+ if(e && e->bIsBIGBuilding && e->m_level == level){
+ mi = CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(!e->bImBeingRendered){
+ e->DeleteRwObject();
+ if(mi->m_refCount == 0)
+ RemoveModel(e->GetModelIndex());
+ }
+ }
+ }
+}
+
+bool
+CStreaming::RemoveLoadedVehicle(void)
+{
+ int i, id;
+
+ for(i = 0; i < MAXVEHICLESLOADED; i++){
+ ms_lastVehicleDeleted++;
+ if(ms_lastVehicleDeleted == MAXVEHICLESLOADED)
+ ms_lastVehicleDeleted = 0;
+ id = ms_vehiclesLoaded[ms_lastVehicleDeleted];
+ if(id != -1 &&
+ (ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 &&
+ CModelInfo::GetModelInfo(id)->m_refCount == 0 &&
+ ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED)
+ goto found;
+ }
+ return false;
+found:
+ RemoveModel(ms_vehiclesLoaded[ms_lastVehicleDeleted]);
+ ms_numVehiclesLoaded--;
+ ms_vehiclesLoaded[ms_lastVehicleDeleted] = -1;
+ return true;
+}
+
+bool
+CStreaming::RemoveLeastUsedModel(void)
+{
+ CStreamingInfo *si;
+ int streamId;
+
+ for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){
+ streamId = si - ms_aInfoForModel;
+ if(streamId < STREAM_OFFSET_TXD){
+ if(CModelInfo::GetModelInfo(streamId)->m_refCount == 0){
+ RemoveModel(streamId);
+ return true;
+ }
+ }else{
+ if(CTxdStore::GetNumRefs(streamId - STREAM_OFFSET_TXD) == 0 &&
+ !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){
+ RemoveModel(streamId);
+ return true;
+ }
+ }
+ }
+ return ms_numVehiclesLoaded > 7 && RemoveLoadedVehicle();
+}
+
+void
+CStreaming::RemoveAllUnusedModels(void)
+{
+ int i;
+
+ for(i = 0; i < MAXVEHICLESLOADED; i++)
+ RemoveLoadedVehicle();
+
+ for(i = NUM_DEFAULT_MODELS; i < MODELINFOSIZE; i++){
+ if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED &&
+ ms_aInfoForModel[i].m_flags & STREAMFLAGS_DONT_REMOVE &&
+ CModelInfo::GetModelInfo(i)->m_refCount == 0){
+ RemoveModel(i);
+ ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED;
+ }
+ }
+}
+
+bool
+CStreaming::RemoveReferencedTxds(int32 mem)
+{
+ CStreamingInfo *si;
+ int streamId;
+
+ for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){
+ streamId = si - ms_aInfoForModel;
+ if(streamId >= STREAM_OFFSET_TXD &&
+ CTxdStore::GetNumRefs(streamId-STREAM_OFFSET_TXD) == 0){
+ RemoveModel(streamId);
+ if(ms_memoryUsed < mem)
+ return true;
+ }
+ }
+ return false;
+}
+
+// TODO: RemoveCurrentZonesModels
+
+void
+CStreaming::RemoveUnusedModelsInLoadedList(void)
+{
+ // empty
+}
+
+bool
+CStreaming::IsTxdUsedByRequestedModels(int32 txdId)
+{
+ CStreamingInfo *si;
+ int streamId;
+ int i;
+
+ for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = si->m_next){
+ streamId = si - ms_aInfoForModel;
+ if(streamId < STREAM_OFFSET_TXD &&
+ CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId)
+ return true;
+ }
+
+ for(i = 0; i < 4; i++){
+ streamId = ms_channel[0].streamIds[i];
+ if(streamId != -1 && streamId < STREAM_OFFSET_TXD &&
+ CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId)
+ return true;
+ streamId = ms_channel[1].streamIds[i];
+ if(streamId != -1 && streamId < STREAM_OFFSET_TXD &&
+ CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId)
+ return true;
+ }
+
+ return false;
+}
+
+int32
+CStreaming::GetAvailableVehicleSlot(void)
+{
+ int i;
+ for(i = 0; i < MAXVEHICLESLOADED; i++)
+ if(ms_vehiclesLoaded[i] == -1)
+ return i;
+ return -1;
+}
+
+bool
+CStreaming::AddToLoadedVehiclesList(int32 modelId)
+{
+ int i;
+ int id;
+
+ if(ms_numVehiclesLoaded < desiredNumVehiclesLoaded){
+ // still room for vehicles
+ for(i = 0; i < MAXVEHICLESLOADED; i++){
+ if(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1)
+ break;
+ ms_lastVehicleDeleted++;
+ if(ms_lastVehicleDeleted == MAXVEHICLESLOADED)
+ ms_lastVehicleDeleted = 0;
+ }
+ assert(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1);
+ ms_numVehiclesLoaded++;
+ }else{
+ // find vehicle we can remove
+ for(i = 0; i < MAXVEHICLESLOADED; i++){
+ id = ms_vehiclesLoaded[ms_lastVehicleDeleted];
+ if(id != -1 &&
+ (ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 &&
+ CModelInfo::GetModelInfo(id)->m_refCount == 0)
+ goto found;
+ ms_lastVehicleDeleted++;
+ if(ms_lastVehicleDeleted == MAXVEHICLESLOADED)
+ ms_lastVehicleDeleted = 0;
+ }
+ id = -1;
+found:
+ if(id == -1){
+ // didn't find anything, try a free slot
+ id = GetAvailableVehicleSlot();
+ if(id == -1)
+ return false; // still no luck
+ ms_lastVehicleDeleted = id;
+ // this is more that we wanted actually
+ ms_numVehiclesLoaded++;
+ }else
+ RemoveModel(id);
+ }
+
+ ms_vehiclesLoaded[ms_lastVehicleDeleted++] = modelId;
+ if(ms_lastVehicleDeleted == MAXVEHICLESLOADED)
+ ms_lastVehicleDeleted = 0;
+ return true;
+}
+
+bool
+CStreaming::IsObjectInCdImage(int32 id)
+{
+ uint32 posn, size;
+ return ms_aInfoForModel[id].GetCdPosnAndSize(posn, size);
+}
+
+void
+CStreaming::HaveAllBigBuildingsLoaded(eLevelName level)
+{
+ int i, n;
+ CEntity *e;
+
+ if(ms_hasLoadedLODs)
+ return;
+
+ if(level == LEVEL_INDUSTRIAL){
+ if(ms_aInfoForModel[islandLODcomInd].m_loadState != STREAMSTATE_LOADED ||
+ ms_aInfoForModel[islandLODsubInd].m_loadState != STREAMSTATE_LOADED)
+ return;
+ }else if(level == LEVEL_COMMERCIAL){
+ if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED ||
+ ms_aInfoForModel[islandLODsubCom].m_loadState != STREAMSTATE_LOADED)
+ return;
+ }else if(level == LEVEL_SUBURBAN){
+ if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED ||
+ ms_aInfoForModel[islandLODcomSub].m_loadState != STREAMSTATE_LOADED)
+ return;
+ }
+
+ n = CPools::GetBuildingPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetBuildingPool()->GetSlot(i);
+ if(e && e->bIsBIGBuilding && e->m_level == level &&
+ ms_aInfoForModel[e->GetModelIndex()].m_loadState != STREAMSTATE_LOADED)
+ return;
+ }
+
+ RemoveUnusedBigBuildings(level);
+ ms_hasLoadedLODs = true;
+}
+
+void
+CStreaming::SetModelIsDeletable(int32 id)
+{
+ ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_DONT_REMOVE;
+ if((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->m_type != MITYPE_VEHICLE) &&
+ (ms_aInfoForModel[id].m_flags & STREAMFLAGS_SCRIPTOWNED) == 0){
+ if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED)
+ RemoveModel(id);
+ else if(ms_aInfoForModel[id].m_next == nil)
+ ms_aInfoForModel[id].AddToList(&ms_startLoadedList);
+ }
+}
+
+void
+CStreaming::SetModelTxdIsDeletable(int32 id)
+{
+ SetModelIsDeletable(CModelInfo::GetModelInfo(id)->GetTxdSlot() + STREAM_OFFSET_TXD);
+}
+
+void
+CStreaming::SetMissionDoesntRequireModel(int32 id)
+{
+ ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_SCRIPTOWNED;
+ if((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->m_type != MITYPE_VEHICLE) &&
+ (ms_aInfoForModel[id].m_flags & STREAMFLAGS_DONT_REMOVE) == 0){
+ if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED)
+ RemoveModel(id);
+ else if(ms_aInfoForModel[id].m_next == nil)
+ ms_aInfoForModel[id].AddToList(&ms_startLoadedList);
+ }
+}
+
+void
+CStreaming::LoadInitialPeds(void)
+{
+ RequestModel(MI_COP, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_TAXI_D, STREAMFLAGS_DONT_REMOVE);
+}
+
+void
+CStreaming::LoadInitialVehicles(void)
+{
+ int id;
+
+ ms_numVehiclesLoaded = 0;
+ ms_lastVehicleDeleted = 0;
+
+ if(CModelInfo::GetModelInfo("taxi", &id))
+ RequestModel(id, STREAMFLAGS_DONT_REMOVE);
+ if(CModelInfo::GetModelInfo("police", &id))
+ RequestModel(id, STREAMFLAGS_DONT_REMOVE);
+}
+
+void
+CStreaming::StreamVehiclesAndPeds(void)
+{
+ int i, model;
+ static int timeBeforeNextLoad = 0;
+ static int modelQualityClass = 0;
+
+ if(CRecordDataForGame::RecordingState == RECORDSTATE_1 ||
+ CRecordDataForGame::RecordingState == RECORDSTATE_2)
+ return;
+
+ if(FindPlayerPed()->m_pWanted->AreSwatRequired()){
+ RequestModel(MI_ENFORCER, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_SWAT, STREAMFLAGS_DONT_REMOVE);
+ }else{
+ SetModelIsDeletable(MI_ENFORCER);
+ if(!HasModelLoaded(MI_ENFORCER))
+ SetModelIsDeletable(MI_SWAT);
+ }
+
+ if(FindPlayerPed()->m_pWanted->AreFbiRequired()){
+ RequestModel(MI_FBICAR, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_FBI, STREAMFLAGS_DONT_REMOVE);
+ }else{
+ SetModelIsDeletable(MI_FBICAR);
+ if(!HasModelLoaded(MI_FBICAR))
+ SetModelIsDeletable(MI_FBI);
+ }
+
+ if(FindPlayerPed()->m_pWanted->AreArmyRequired()){
+ RequestModel(MI_RHINO, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_BARRACKS, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_ARMY, STREAMFLAGS_DONT_REMOVE);
+ }else{
+ SetModelIsDeletable(MI_RHINO);
+ SetModelIsDeletable(MI_BARRACKS);
+ if(!HasModelLoaded(MI_RHINO) && !HasModelLoaded(MI_BARRACKS))
+ SetModelIsDeletable(MI_ARMY);
+ }
+
+ if(FindPlayerPed()->m_pWanted->NumOfHelisRequired() > 0)
+ RequestModel(MI_CHOPPER, STREAMFLAGS_DONT_REMOVE);
+ else
+ SetModelIsDeletable(MI_CHOPPER);
+
+ if(timeBeforeNextLoad >= 0)
+ timeBeforeNextLoad--;
+ else if(ms_numVehiclesLoaded <= desiredNumVehiclesLoaded){
+ for(i = 0; i <= 10; i++){
+ model = CCarCtrl::ChooseCarModel(modelQualityClass);
+ modelQualityClass++;
+ if(modelQualityClass >= NUM_VEHICLE_CLASSES)
+ modelQualityClass = 0;
+
+ // check if we want to load this model
+ if(ms_aInfoForModel[model].m_loadState == STREAMSTATE_NOTLOADED &&
+ ((CVehicleModelInfo*)CModelInfo::GetModelInfo(model))->m_level & (1 << (CGame::currLevel-1)))
+ break;
+ }
+
+ if(i <= 10){
+ RequestModel(model, STREAMFLAGS_DEPENDENCY);
+ timeBeforeNextLoad = 500;
+ }
+ }
+}
+
+void
+CStreaming::StreamZoneModels(const CVector &pos)
+{
+ int i;
+ uint16 gangsToLoad, gangCarsToLoad, bit;
+ CZoneInfo info;
+
+ CTheZones::GetZoneInfoForTimeOfDay(&pos, &info);
+
+ if(info.pedGroup != ms_currentPedGrp){
+
+ // unload pevious group
+ if(ms_currentPedGrp != -1)
+ for(i = 0; i < 8; i++){
+ if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1)
+ break;
+ SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]);
+ SetModelTxdIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]);
+ }
+
+ ms_currentPedGrp = info.pedGroup;
+
+ for(i = 0; i < 8; i++){
+ if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1)
+ break;
+ RequestModel(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i], STREAMFLAGS_DONT_REMOVE);
+ }
+ }
+ RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE);
+
+ gangsToLoad = 0;
+ gangCarsToLoad = 0;
+ if(info.gangDensity[0] != 0) gangsToLoad |= 1<<0;
+ if(info.gangDensity[1] != 0) gangsToLoad |= 1<<1;
+ if(info.gangDensity[2] != 0) gangsToLoad |= 1<<2;
+ if(info.gangDensity[3] != 0) gangsToLoad |= 1<<3;
+ if(info.gangDensity[4] != 0) gangsToLoad |= 1<<4;
+ if(info.gangDensity[5] != 0) gangsToLoad |= 1<<5;
+ if(info.gangDensity[6] != 0) gangsToLoad |= 1<<6;
+ if(info.gangDensity[7] != 0) gangsToLoad |= 1<<7;
+ if(info.gangDensity[8] != 0) gangsToLoad |= 1<<8;
+ if(info.gangThreshold[0] != info.copDensity) gangCarsToLoad |= 1<<0;
+ if(info.gangThreshold[1] != info.gangThreshold[0]) gangCarsToLoad |= 1<<1;
+ if(info.gangThreshold[2] != info.gangThreshold[1]) gangCarsToLoad |= 1<<2;
+ if(info.gangThreshold[3] != info.gangThreshold[2]) gangCarsToLoad |= 1<<3;
+ if(info.gangThreshold[4] != info.gangThreshold[3]) gangCarsToLoad |= 1<<4;
+ if(info.gangThreshold[5] != info.gangThreshold[4]) gangCarsToLoad |= 1<<5;
+ if(info.gangThreshold[6] != info.gangThreshold[5]) gangCarsToLoad |= 1<<6;
+ if(info.gangThreshold[7] != info.gangThreshold[6]) gangCarsToLoad |= 1<<7;
+ if(info.gangThreshold[8] != info.gangThreshold[7]) gangCarsToLoad |= 1<<8;
+
+ if(gangsToLoad == ms_loadedGangs && gangCarsToLoad == ms_loadedGangCars)
+ return;
+
+ // This makes things simpler than the game does it
+ gangsToLoad |= gangCarsToLoad;
+
+ for(i = 0; i < NUM_GANGS; i++){
+ bit = 1<<i;
+
+ if(gangsToLoad & bit && (ms_loadedGangs & bit) == 0){
+ RequestModel(MI_GANG01 + i*2, STREAMFLAGS_DONT_REMOVE);
+ RequestModel(MI_GANG01 + i*2 + 1, STREAMFLAGS_DONT_REMOVE);
+ ms_loadedGangs |= bit;
+ }else if((gangsToLoad & bit) == 0 && ms_loadedGangs & bit){
+ SetModelIsDeletable(MI_GANG01 + i*2);
+ SetModelIsDeletable(MI_GANG01 + i*2 + 1);
+ SetModelTxdIsDeletable(MI_GANG01 + i*2);
+ SetModelTxdIsDeletable(MI_GANG01 + i*2 + 1);
+ ms_loadedGangs &= ~bit;
+ }
+
+ if(gangCarsToLoad & bit && (ms_loadedGangCars & bit) == 0){
+ RequestModel(CGangs::GetGangInfo(i)->m_nVehicleMI, STREAMFLAGS_DONT_REMOVE);
+ }else if((gangCarsToLoad & bit) == 0 && ms_loadedGangCars & bit){
+ SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI);
+ SetModelTxdIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI);
+ }
+ }
+ ms_loadedGangCars = gangCarsToLoad;
+}
+
+void
+CStreaming::RemoveCurrentZonesModels(void)
+{
+ int i;
+
+ if(ms_currentPedGrp != -1)
+ for(i = 0; i < 8; i++){
+ if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1)
+ break;
+ if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != MI_MALE01)
+ SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]);
+ }
+
+ for(i = 0; i < NUM_GANGS; i++){
+ SetModelIsDeletable(MI_GANG01 + i*2);
+ SetModelIsDeletable(MI_GANG01 + i*2 + 1);
+ if(CGangs::GetGangInfo(i)->m_nVehicleMI != -1)
+ SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI);
+ }
+
+ ms_currentPedGrp = -1;
+ ms_loadedGangs = 0;
+ ms_loadedGangCars = 0;
+}
+
+
+
+// Find starting offset of the cdimage we next want to read
+// Not useful at all on PC...
+int32
+CStreaming::GetCdImageOffset(int32 lastPosn)
+{
+ int offset, off;
+ int i, img;
+ int dist, mindist;
+
+ img = -1;
+ mindist = INT_MAX;
+ offset = ms_imageOffsets[ms_lastImageRead];
+ if(lastPosn <= offset || lastPosn > offset + ms_imageSize){
+ // last read position is not in last image
+ for(i = 0; i < NUMCDIMAGES; i++){
+ off = ms_imageOffsets[i];
+ if(off == -1) continue;
+ if((uint32)lastPosn > (uint32)off)
+ // after start of image, get distance from end
+ // negative if before end!
+ dist = lastPosn - (off + ms_imageSize);
+ else
+ // before image, get offset to start
+ // this will never be negative
+ dist = off - lastPosn;
+ if(dist < mindist){
+ img = i;
+ mindist = dist;
+ }
+ }
+ assert(img >= 0);
+ offset = ms_imageOffsets[img];
+ ms_lastImageRead = img;
+ }
+ return offset;
+}
+
+inline bool
+TxdAvailable(int32 txdId)
+{
+ CStreamingInfo *si = &CStreaming::ms_aInfoForModel[txdId + STREAM_OFFSET_TXD];
+ return si->m_loadState == STREAMSTATE_LOADED || si->m_loadState == STREAMSTATE_READING;
+}
+
+// Find stream id of next requested file in cdimage
+int32
+CStreaming::GetNextFileOnCd(int32 lastPosn, bool priority)
+{
+ CStreamingInfo *si, *next;
+ int streamId;
+ uint32 posn, size;
+ int streamIdFirst, streamIdNext;
+ uint32 posnFirst, posnNext;
+
+ streamIdFirst = -1;
+ streamIdNext = -1;
+ posnFirst = UINT_MAX;
+ posnNext = UINT_MAX;
+
+ for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){
+ next = si->m_next;
+ streamId = si - ms_aInfoForModel;
+
+ // only priority requests if there are any
+ if(priority && ms_numPriorityRequests != 0 && !si->IsPriority())
+ continue;
+
+ // request Txd if necessary
+ if(streamId < STREAM_OFFSET_TXD &&
+ !TxdAvailable(CModelInfo::GetModelInfo(streamId)->GetTxdSlot())){
+ ReRequestTxd(CModelInfo::GetModelInfo(streamId)->GetTxdSlot());
+ }else if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){
+ if(posn < posnFirst){
+ // find first requested file in image
+ streamIdFirst = streamId;
+ posnFirst = posn;
+ }
+ if(posn < posnNext && posn >= (uint32)lastPosn){
+ // find first requested file after last read position
+ streamIdNext = streamId;
+ posnNext = posn;
+ }
+ }else{
+ // empty file
+ DecrementRef(streamId);
+ si->RemoveFromList();
+ si->m_loadState = STREAMSTATE_LOADED;
+ }
+ }
+
+ // wrap around
+ if(streamIdNext == -1)
+ streamIdNext = streamIdFirst;
+
+ if(streamIdNext == -1 && ms_numPriorityRequests != 0){
+ // try non-priority files
+ ms_numPriorityRequests = 0;
+ streamIdNext = GetNextFileOnCd(lastPosn, false);
+ }
+
+ return streamIdNext;
+}
+
+/*
+ * Streaming buffer size is half of the largest file.
+ * Files larger than the buffer size can only be loaded by channel 0,
+ * which then uses both buffers, while channel 1 is idle.
+ * ms_bLoadingBigModel is set to true to indicate this state.
+ *
+ * TODO: two-part files
+ */
+
+// Make channel read from disc
+void
+CStreaming::RequestModelStream(int32 ch)
+{
+ int lastPosn, imgOffset, streamId;
+ int totalSize;
+ uint32 posn, size, unused;
+ int i;
+ int haveBigFile, havePed;
+
+ lastPosn = CdStreamGetLastPosn();
+ imgOffset = GetCdImageOffset(lastPosn);
+ streamId = GetNextFileOnCd(lastPosn - imgOffset, true);
+
+ if(streamId == -1)
+ return;
+
+ // remove Txds that aren't requested anymore
+ while(streamId >= STREAM_OFFSET_TXD){
+ if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY ||
+ IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD))
+ break;
+ RemoveModel(streamId);
+ // so try next file
+ ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size);
+ streamId = GetNextFileOnCd(posn + size, true);
+ }
+
+ if(streamId == -1)
+ return;
+
+ ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size);
+ if(size > (uint32)ms_streamingBufferSize){
+ // Can only load big models on channel 0, and 1 has to be idle
+ if(ch == 1 || ms_channel[1].state != CHANNELSTATE_IDLE)
+ return;
+ ms_bLoadingBigModel = true;
+ }
+
+ // Load up to 4 adjacent files
+ haveBigFile = 0;
+ havePed = 0;
+ totalSize = 0;
+ for(i = 0; i < 4; i++){
+ // no more files we can read
+ if(streamId == -1 || ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_INQUEUE)
+ break;
+
+ // also stop at non-priority files
+ ms_aInfoForModel[streamId].GetCdPosnAndSize(unused, size);
+ if(ms_numPriorityRequests != 0 && !ms_aInfoForModel[streamId].IsPriority())
+ break;
+
+ // Can't load certain combinations of files together
+ if(streamId < STREAM_OFFSET_TXD){
+ if(havePed && CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_PED ||
+ haveBigFile && CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_VEHICLE ||
+ !TxdAvailable(CModelInfo::GetModelInfo(streamId)->GetTxdSlot()))
+ break;
+ }else{
+ if(haveBigFile && size > 200)
+ break;
+ }
+
+ // Now add the file
+ ms_channel[ch].streamIds[i] = streamId;
+ ms_channel[ch].offsets[i] = totalSize;
+ totalSize += size;
+
+ // To big for buffer, remove again
+ if(totalSize > ms_streamingBufferSize && i > 0){
+ totalSize -= size;
+ break;
+ }
+ if(streamId < STREAM_OFFSET_TXD){
+ if(CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_PED)
+ havePed = 1;
+ if(CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_VEHICLE)
+ haveBigFile = 1;
+ }else{
+ if(size > 200)
+ haveBigFile = 1;
+ }
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING;
+ ms_aInfoForModel[streamId].RemoveFromList();
+ DecrementRef(streamId);
+
+ streamId = ms_aInfoForModel[streamId].m_nextID;
+ }
+
+ // clear remaining slots
+ for(; i < 4; i++)
+ ms_channel[ch].streamIds[i] = -1;
+ // Now read the data
+ assert(!(ms_bLoadingBigModel && ch == 1)); // this would clobber the buffer
+ if(CdStreamRead(ch, ms_pStreamingBuffer[ch], imgOffset+posn, totalSize) == STREAM_NONE)
+ debug("FUCKFUCKFUCK\n");
+ ms_channel[ch].state = CHANNELSTATE_READING;
+ ms_channel[ch].field24 = 0;
+ ms_channel[ch].size = totalSize;
+ ms_channel[ch].position = imgOffset+posn;
+ ms_channel[ch].numTries = 0;
+}
+
+// Load data previously read from disc
+bool
+CStreaming::ProcessLoadingChannel(int32 ch)
+{
+ int status;
+ int i, id, cdsize;
+
+ status = CdStreamGetStatus(ch);
+ if(status != STREAM_NONE){
+ // busy
+ if(status != STREAM_READING && status != STREAM_WAITING){
+ ms_channelError = ch;
+ ms_channel[ch].state = CHANNELSTATE_ERROR;
+ ms_channel[ch].status = status;
+ }
+ return false;
+ }
+
+ if(ms_channel[ch].state == CHANNELSTATE_STARTED){
+ ms_channel[ch].state = CHANNELSTATE_IDLE;
+ FinishLoadingLargeFile(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[0]*CDSTREAM_SECTOR_SIZE],
+ ms_channel[ch].streamIds[0]);
+ ms_channel[ch].streamIds[0] = -1;
+ }else{
+ ms_channel[ch].state = CHANNELSTATE_IDLE;
+ for(i = 0; i < 4; i++){
+ id = ms_channel[ch].streamIds[i];
+ if(id == -1)
+ continue;
+
+ cdsize = ms_aInfoForModel[id].GetCdSize();
+ if(id < STREAM_OFFSET_TXD &&
+ CModelInfo::GetModelInfo(id)->m_type == MITYPE_VEHICLE &&
+ ms_numVehiclesLoaded >= desiredNumVehiclesLoaded &&
+ !RemoveLoadedVehicle() &&
+ ((ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 || GetAvailableVehicleSlot() == -1)){
+ // can't load vehicle
+ RemoveModel(id);
+ if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST)
+ ReRequestModel(id);
+ else if(CTxdStore::GetNumRefs(CModelInfo::GetModelInfo(id)->GetTxdSlot()) == 0)
+ RemoveTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot());
+ }else{
+ MakeSpaceFor(cdsize * CDSTREAM_SECTOR_SIZE);
+ ConvertBufferToObject(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[i]*CDSTREAM_SECTOR_SIZE],
+ id);
+ if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){
+ // queue for second part
+ ms_channel[ch].state = CHANNELSTATE_STARTED;
+ ms_channel[ch].offsets[0] = ms_channel[ch].offsets[i];
+ ms_channel[ch].streamIds[0] = id;
+ if(i != 0)
+ ms_channel[ch].streamIds[i] = -1;
+ }else
+ ms_channel[ch].streamIds[i] = -1;
+ }
+ }
+ }
+
+ if(ms_bLoadingBigModel && ms_channel[ch].state != CHANNELSTATE_STARTED){
+ ms_bLoadingBigModel = false;
+ // reset channel 1 after loading a big model
+ for(i = 0; i < 4; i++)
+ ms_channel[1].streamIds[i] = -1;
+ ms_channel[1].state = CHANNELSTATE_IDLE;
+ }
+
+ return true;
+}
+
+void
+CStreaming::RetryLoadFile(int32 ch)
+{
+ char *key;
+
+ CPad::StopPadsShaking();
+
+ if(ms_channel[ch].numTries >= 3){
+ switch(ms_channel[ch].status){
+ case STREAM_ERROR_NOCD: key = "NOCD"; break;
+ case STREAM_ERROR_OPENCD: key = "OPENCD"; break;
+ case STREAM_ERROR_WRONGCD: key = "WRONGCD"; break;
+ default: key = "CDERROR"; break;
+ }
+ CHud::SetMessage(TheText.Get(key));
+ CTimer::SetCodePause(true);
+ }
+
+ switch(ms_channel[ch].state){
+ case CHANNELSTATE_IDLE:
+streamread:
+ CdStreamRead(ch, ms_pStreamingBuffer[ch], ms_channel[ch].position, ms_channel[ch].size);
+ ms_channel[ch].state = CHANNELSTATE_READING;
+ ms_channel[ch].field24 = -600;
+ break;
+ case CHANNELSTATE_READING:
+ if(ProcessLoadingChannel(ch)){
+ ms_channelError = -1;
+ CTimer::SetCodePause(false);
+ }
+ break;
+ case CHANNELSTATE_ERROR:
+ ms_channel[ch].numTries++;
+ if(CdStreamGetStatus(ch) != STREAM_READING && CdStreamGetStatus(ch) != STREAM_WAITING)
+ goto streamread;
+ break;
+ }
+}
+
+void
+CStreaming::LoadRequestedModels(void)
+{
+ static int currentChannel = 0;
+
+ // We can't read with channel 1 while channel 0 is using its buffer
+ if(ms_bLoadingBigModel)
+ currentChannel = 0;
+
+ // We have data, load
+ if(ms_channel[currentChannel].state == CHANNELSTATE_READING ||
+ ms_channel[currentChannel].state == CHANNELSTATE_STARTED)
+ ProcessLoadingChannel(currentChannel);
+
+ if(ms_channelError == -1){
+ // Channel is idle, read more data
+ if(ms_channel[currentChannel].state == CHANNELSTATE_IDLE)
+ RequestModelStream(currentChannel);
+ // Switch channel
+ if(ms_channel[currentChannel].state != CHANNELSTATE_STARTED)
+ currentChannel = 1 - currentChannel;
+ }
+}
+
+void
+CStreaming::LoadAllRequestedModels(bool priority)
+{
+ static bool bInsideLoadAll = false;
+ int imgOffset, streamId, status;
+ int i;
+ uint32 posn, size;
+
+ if(bInsideLoadAll)
+ return;
+
+ FlushChannels();
+ imgOffset = GetCdImageOffset(CdStreamGetLastPosn());
+
+ while(ms_endRequestedList.m_prev != &ms_startRequestedList){
+ streamId = GetNextFileOnCd(0, priority);
+ if(streamId == -1)
+ break;
+
+ ms_aInfoForModel[streamId].RemoveFromList();
+ DecrementRef(streamId);
+
+ if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){
+ do
+ status = CdStreamRead(0, ms_pStreamingBuffer[0], imgOffset+posn, size);
+ while(CdStreamSync(0) || status == STREAM_NONE);
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING;
+
+ MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE);
+ ConvertBufferToObject(ms_pStreamingBuffer[0], streamId);
+ if(ms_aInfoForModel[streamId].m_loadState == STREAMSTATE_STARTED)
+ FinishLoadingLargeFile(ms_pStreamingBuffer[0], streamId);
+
+ if(streamId < STREAM_OFFSET_TXD){
+ CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(streamId);
+ if(mi->IsSimple())
+ mi->m_alpha = 255;
+ }
+ }else{
+ // empty
+ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED;
+ }
+ }
+
+ ms_bLoadingBigModel = false;
+ for(i = 0; i < 4; i++){
+ ms_channel[1].streamIds[i] = -1;
+ ms_channel[1].offsets[i] = -1;
+ }
+ ms_channel[1].state = CHANNELSTATE_IDLE;
+ bInsideLoadAll = false;
+}
+
+void
+CStreaming::FlushChannels(void)
+{
+ if(ms_channel[1].state == CHANNELSTATE_STARTED)
+ ProcessLoadingChannel(1);
+
+ if(ms_channel[0].state == CHANNELSTATE_READING){
+ CdStreamSync(0);
+ ProcessLoadingChannel(0);
+ }
+ if(ms_channel[0].state == CHANNELSTATE_STARTED)
+ ProcessLoadingChannel(0);
+
+ if(ms_channel[1].state == CHANNELSTATE_READING){
+ CdStreamSync(1);
+ ProcessLoadingChannel(1);
+ }
+ if(ms_channel[1].state == CHANNELSTATE_STARTED)
+ ProcessLoadingChannel(1);
+}
+
+void
+CStreaming::FlushRequestList(void)
+{
+ CStreamingInfo *si, *next;
+
+ for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){
+ next = si->m_next;
+ RemoveModel(si - ms_aInfoForModel);
+ }
+ FlushChannels();
+}
+
+
+void
+CStreaming::ImGonnaUseStreamingMemory(void)
+{
+ // empty
+}
+
+void
+CStreaming::IHaveUsedStreamingMemory(void)
+{
+ UpdateMemoryUsed();
+}
+
+void
+CStreaming::UpdateMemoryUsed(void)
+{
+ // empty
+}
+
+#define STREAM_DIST (2*SECTOR_SIZE_X)
+
+void
+CStreaming::AddModelsToRequestList(const CVector &pos)
+{
+ float xmin, xmax, ymin, ymax;
+ int ixmin, ixmax, iymin, iymax;
+ int ix, iy;
+ int dx, dy, d;
+ CSector *sect;
+
+ xmin = pos.x - STREAM_DIST;
+ ymin = pos.y - STREAM_DIST;
+ xmax = pos.x + STREAM_DIST;
+ ymax = pos.y + STREAM_DIST;
+
+ ixmin = CWorld::GetSectorIndexX(xmin);
+ if(ixmin < 0) ixmin = 0;
+ ixmax = CWorld::GetSectorIndexX(xmax);
+ if(ixmax >= NUMSECTORS_X) ixmax = NUMSECTORS_X-1;
+ iymin = CWorld::GetSectorIndexY(ymin);
+ if(iymin < 0) iymin = 0;
+ iymax = CWorld::GetSectorIndexY(ymax);
+ if(iymax >= NUMSECTORS_Y) iymax = NUMSECTORS_Y-1;
+
+ CWorld::AdvanceCurrentScanCode();
+
+ for(iy = iymin; iy < iymax; iy++){
+ dy = iy - CWorld::GetSectorIndexY(pos.y);
+ for(ix = ixmin; ix < ixmax; ix++){
+
+ if(CRenderer::m_loadingPriority && ms_numModelsRequested > 5)
+ return;
+
+ dx = ix - CWorld::GetSectorIndexX(pos.x);
+ d = dx*dx + dy*dy;
+ sect = CWorld::GetSector(ix, iy);
+ if(d <= 1){
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ }else if(d <= 4*4){
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], pos.x, pos.y, xmin, ymin, xmax, ymax);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], pos.x, pos.y, xmin, ymin, xmax, ymax);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], pos.x, pos.y, xmin, ymin, xmax, ymax);
+ ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], pos.x, pos.y, xmin, ymin, xmax, ymax);
+ }
+ }
+ }
+}
+
+void
+CStreaming::ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax)
+{
+ CPtrNode *node;
+ CEntity *e;
+ float lodDistSq;
+ CVector2D pos;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+
+ if(e->m_scanCode == CWorld::GetCurrentScanCode())
+ continue;
+
+ e->m_scanCode = CWorld::GetCurrentScanCode();
+ if(!e->bStreamingDontDelete && !e->bIsSubway &&
+ (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){
+ CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(mi->m_type != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())){
+ lodDistSq = sq(mi->GetLargestLodDistance());
+ lodDistSq = min(lodDistSq, sq(STREAM_DIST));
+ pos = CVector2D(e->GetPosition());
+ if(xmin < pos.x && pos.x < xmax &&
+ ymin < pos.y && pos.y < ymax &&
+ (CVector2D(x, y) - pos).MagnitudeSqr() < lodDistSq)
+ if(CRenderer::IsEntityCullZoneVisible(e))
+ RequestModel(e->GetModelIndex(), 0);
+ }
+ }
+ }
+}
+
+void
+CStreaming::ProcessEntitiesInSectorList(CPtrList &list)
+{
+ CPtrNode *node;
+ CEntity *e;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+
+ if(e->m_scanCode == CWorld::GetCurrentScanCode())
+ continue;
+
+ e->m_scanCode = CWorld::GetCurrentScanCode();
+ if(!e->bStreamingDontDelete && !e->bIsSubway &&
+ (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){
+ CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex());
+ if(mi->m_type != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff()))
+ if(CRenderer::IsEntityCullZoneVisible(e))
+ RequestModel(e->GetModelIndex(), 0);
+ }
+ }
+}
+
+void
+CStreaming::DeleteFarAwayRwObjects(const CVector &pos)
+{
+ int posx, posy;
+ int x, y;
+ int r, i;
+ CSector *sect;
+
+ posx = CWorld::GetSectorIndexX(pos.x);
+ posy = CWorld::GetSectorIndexY(pos.y);
+
+ // Move oldSectorX/Y to new sector and delete RW objects in its "wake" for every step:
+ // O is the old sector, <- is the direction in which we move it,
+ // X are the sectors we delete RW objects from (except we go up to 10)
+ // X
+ // X X
+ // X X X
+ // X X X
+ // <- O X X X
+ // X X X
+ // X X X
+ // X X
+ // X
+
+ while(posx != ms_oldSectorX){
+ if(posx < ms_oldSectorX){
+ for(r = 2; r <= 10; r++){
+ x = ms_oldSectorX + r;
+ if(x < 0)
+ continue;
+ if(x >= NUMSECTORS_X)
+ break;
+
+ for(i = -r; i <= r; i++){
+ y = ms_oldSectorY + i;
+ if(y < 0)
+ continue;
+ if(y >= NUMSECTORS_Y)
+ break;
+
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ }
+ }
+ ms_oldSectorX--;
+ }else{
+ for(r = 2; r <= 10; r++){
+ x = ms_oldSectorX - r;
+ if(x < 0)
+ break;
+ if(x >= NUMSECTORS_X)
+ continue;
+
+ for(i = -r; i <= r; i++){
+ y = ms_oldSectorY + i;
+ if(y < 0)
+ continue;
+ if(y >= NUMSECTORS_Y)
+ break;
+
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ }
+ }
+ ms_oldSectorX++;
+ }
+ }
+
+ while(posy != ms_oldSectorY){
+ if(posy < ms_oldSectorY){
+ for(r = 2; r <= 10; r++){
+ y = ms_oldSectorY + r;
+ if(y < 0)
+ continue;
+ if(y >= NUMSECTORS_Y)
+ break;
+
+ for(i = -r; i <= r; i++){
+ x = ms_oldSectorX + i;
+ if(x < 0)
+ continue;
+ if(x >= NUMSECTORS_X)
+ break;
+
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ }
+ }
+ ms_oldSectorY--;
+ }else{
+ for(r = 2; r <= 10; r++){
+ y = ms_oldSectorY - r;
+ if(y < 0)
+ break;
+ if(y >= NUMSECTORS_Y)
+ continue;
+
+ for(i = -r; i <= r; i++){
+ x = ms_oldSectorX + i;
+ if(x < 0)
+ continue;
+ if(x >= NUMSECTORS_X)
+ break;
+
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ }
+ }
+ ms_oldSectorY++;
+ }
+ }
+}
+
+void
+CStreaming::DeleteAllRwObjects(void)
+{
+ int x, y;
+ CSector *sect;
+
+ for(x = 0; x < NUMSECTORS_X; x++)
+ for(y = 0; y < NUMSECTORS_Y; y++){
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]);
+ }
+}
+
+void
+CStreaming::DeleteRwObjectsAfterDeath(const CVector &pos)
+{
+ int ix, iy;
+ int x, y;
+ CSector *sect;
+
+ ix = CWorld::GetSectorIndexX(pos.x);
+ iy = CWorld::GetSectorIndexX(pos.y);
+
+ for(x = 0; x < NUMSECTORS_X; x++)
+ for(y = 0; y < NUMSECTORS_Y; y++)
+ if(fabs(ix - x) > 3.0f &&
+ fabs(iy - y) > 3.0f){
+ sect = CWorld::GetSector(x, y);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]);
+ DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]);
+ }
+}
+
+void
+CStreaming::DeleteRwObjectsBehindCamera(int32 mem)
+{
+ int ix, iy;
+ int x, y;
+ int xmin, xmax, ymin, ymax;
+ int inc;
+ CSector *sect;
+
+ if(ms_memoryUsed < mem)
+ return;
+
+ ix = CWorld::GetSectorIndexX(TheCamera.GetPosition().x);
+ iy = CWorld::GetSectorIndexX(TheCamera.GetPosition().y);
+
+ if(fabs(TheCamera.GetForward().x) > fabs(TheCamera.GetForward().y)){
+ // looking west/east
+
+ ymin = max(iy - 10, 0);
+ ymax = min(iy + 10, NUMSECTORS_Y);
+ assert(ymin <= ymax);
+
+ // Delete a block of sectors that we know is behind the camera
+ if(TheCamera.GetForward().x > 0){
+ // looking east
+ xmax = max(ix - 2, 0);
+ xmin = max(ix - 10, 0);
+ inc = 1;
+ }else{
+ // looking west
+ xmax = min(ix + 2, NUMSECTORS_X);
+ xmin = min(ix + 10, NUMSECTORS_X);
+ inc = -1;
+ }
+ for(y = ymin; y <= ymax; y++){
+ for(x = xmin; x != xmax; x += inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+
+ // Now a block that intersects with the camera's frustum
+ if(TheCamera.GetForward().x > 0){
+ // looking east
+ xmax = max(ix + 10, 0);
+ xmin = max(ix - 2, 0);
+ inc = 1;
+ }else{
+ // looking west
+ xmax = min(ix - 10, NUMSECTORS_X);
+ xmin = min(ix + 2, NUMSECTORS_X);
+ inc = -1;
+ }
+ for(y = ymin; y <= ymax; y++){
+ for(x = xmin; x != xmax; x += inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+
+ if(RemoveReferencedTxds(mem))
+ return;
+
+ // As last resort, delete objects from the last step more aggressively
+ for(y = ymin; y <= ymax; y++){
+ for(x = xmax; x != xmin; x -= inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+ }else{
+ // looking north/south
+
+ xmin = max(ix - 10, 0);
+ xmax = min(ix + 10, NUMSECTORS_X);
+ assert(xmin <= xmax);
+
+ // Delete a block of sectors that we know is behind the camera
+ if(TheCamera.GetForward().y > 0){
+ // looking north
+ ymax = max(iy - 2, 0);
+ ymin = max(iy - 10, 0);
+ inc = 1;
+ }else{
+ // looking south
+ ymax = min(iy + 2, NUMSECTORS_Y);
+ ymin = min(iy + 10, NUMSECTORS_Y);
+ inc = -1;
+ }
+ for(x = xmin; x <= xmax; x++){
+ for(y = ymin; y != ymax; y += inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+
+ // Now a block that intersects with the camera's frustum
+ if(TheCamera.GetForward().y > 0){
+ // looking north
+ ymax = max(iy + 10, 0);
+ ymin = max(iy - 2, 0);
+ inc = 1;
+ }else{
+ // looking south
+ ymax = min(iy - 10, NUMSECTORS_Y);
+ ymin = min(iy + 2, NUMSECTORS_Y);
+ inc = -1;
+ }
+ for(x = xmin; x <= xmax; x++){
+ for(y = ymin; y != ymax; y += inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+
+ if(RemoveReferencedTxds(mem))
+ return;
+
+ // As last resort, delete objects from the last step more aggressively
+ for(x = xmin; x <= xmax; x++){
+ for(y = ymax; y != ymin; y -= inc){
+ sect = CWorld::GetSector(x, y);
+ if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) ||
+ DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem))
+ return;
+ }
+ }
+ }
+}
+
+void
+CStreaming::DeleteRwObjectsInSectorList(CPtrList &list)
+{
+ CPtrNode *node;
+ CEntity *e;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(!e->bStreamingDontDelete && !e->bImBeingRendered)
+ e->DeleteRwObject();
+ }
+}
+
+void
+CStreaming::DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y)
+{
+ CPtrNode *node;
+ CEntity *e;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(e->m_rwObject && !e->bStreamingDontDelete && !e->bImBeingRendered){
+ // Now this is pretty weird...
+ if(fabs(CWorld::GetSectorIndexX(e->GetPosition().x) - x) >= 2.0f)
+// {
+ e->DeleteRwObject();
+// return; // BUG?
+// }
+ else // FIX?
+ if(fabs(CWorld::GetSectorIndexY(e->GetPosition().y) - y) >= 2.0f)
+ e->DeleteRwObject();
+ }
+ }
+}
+
+bool
+CStreaming::DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, int32 mem)
+{
+ CPtrNode *node;
+ CEntity *e;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(!e->bStreamingDontDelete && !e->bImBeingRendered &&
+ e->m_rwObject && ms_aInfoForModel[e->GetModelIndex()].m_next){
+ e->DeleteRwObject();
+ if(CModelInfo::GetModelInfo(e->GetModelIndex())->m_refCount == 0){
+ RemoveModel(e->GetModelIndex());
+ if(ms_memoryUsed < mem)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+CStreaming::DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, int32 mem)
+{
+ CPtrNode *node;
+ CEntity *e;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(!e->bStreamingDontDelete && !e->bImBeingRendered &&
+ e->m_rwObject && !e->IsVisible() && ms_aInfoForModel[e->GetModelIndex()].m_next){
+ e->DeleteRwObject();
+ if(CModelInfo::GetModelInfo(e->GetModelIndex())->m_refCount == 0){
+ RemoveModel(e->GetModelIndex());
+ if(ms_memoryUsed < mem)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+CStreaming::MakeSpaceFor(int32 size)
+{
+ // BUG: ms_memoryAvailable can be uninitialized
+ // the code still happens to work in that case because ms_memoryAvailable is unsigned
+ // but it's not nice....
+
+ while((uint32)ms_memoryUsed >= ms_memoryAvailable - size)
+ if(!RemoveLeastUsedModel()){
+ DeleteRwObjectsBehindCamera(ms_memoryAvailable - size);
+ return;
+ }
+}
+
+void
+CStreaming::LoadScene(const CVector &pos)
+{
+ CStreamingInfo *si, *prev;
+ eLevelName level;
+
+ level = CTheZones::GetLevelFromPosition(pos);
+ debug("Start load scene\n");
+ for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){
+ prev = si->m_prev;
+ if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0)
+ RemoveModel(si - ms_aInfoForModel);
+ }
+ CRenderer::m_loadingPriority = false;
+ CCullZones::ForceCullZoneCoors(pos);
+ DeleteAllRwObjects();
+ AddModelsToRequestList(pos);
+ CRadar::StreamRadarSections(pos);
+ RemoveUnusedBigBuildings(level);
+ RequestBigBuildings(level);
+ LoadAllRequestedModels(false);
+ debug("End load scene\n");
+}
+
+void
+CStreaming::MemoryCardSave(uint8 *buffer, uint32 *length)
+{
+ int i;
+
+ *length = NUM_DEFAULT_MODELS;
+ for(i = 0; i < NUM_DEFAULT_MODELS; i++)
+ if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED)
+ buffer[i] = ms_aInfoForModel[i].m_flags;
+ else
+ buffer[i] = 0xFF;
+}
+
+void
+CStreaming::MemoryCardLoad(uint8 *buffer, uint32 length)
+{
+ uint32 i;
+
+ assert(length == NUM_DEFAULT_MODELS);
+ for(i = 0; i < length; i++)
+ if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED)
+ if(buffer[i] != 0xFF)
+ ms_aInfoForModel[i].m_flags = buffer[i];
+}
+
+STARTPATCHES
+ InjectHook(0x406430, CStreaming::Init, PATCH_JUMP);
+ InjectHook(0x406C80, CStreaming::Shutdown, PATCH_JUMP);
+ InjectHook(0x4076C0, CStreaming::Update, PATCH_JUMP);
+ InjectHook(0x406CC0, (void (*)(void))CStreaming::LoadCdDirectory, PATCH_JUMP);
+ InjectHook(0x406DA0, (void (*)(const char*, int))CStreaming::LoadCdDirectory, PATCH_JUMP);
+ InjectHook(0x409740, CStreaming::ConvertBufferToObject, PATCH_JUMP);
+ InjectHook(0x409580, CStreaming::FinishLoadingLargeFile, PATCH_JUMP);
+ InjectHook(0x407EA0, CStreaming::RequestModel, PATCH_JUMP);
+ InjectHook(0x407FD0, CStreaming::RequestSubway, PATCH_JUMP);
+ InjectHook(0x408190, CStreaming::RequestBigBuildings, PATCH_JUMP);
+ InjectHook(0x408210, CStreaming::RequestIslands, PATCH_JUMP);
+ InjectHook(0x40A890, CStreaming::RequestSpecialModel, PATCH_JUMP);
+ InjectHook(0x40ADA0, CStreaming::RequestSpecialChar, PATCH_JUMP);
+ InjectHook(0x54A5F0, CStreaming::HasModelLoaded, PATCH_JUMP);
+ InjectHook(0x40ADC0, CStreaming::HasSpecialCharLoaded, PATCH_JUMP);
+ InjectHook(0x40ADE0, CStreaming::SetMissionDoesntRequireSpecialChar, PATCH_JUMP);
+
+ InjectHook(0x408830, CStreaming::RemoveModel, PATCH_JUMP);
+ InjectHook(0x4083A0, CStreaming::RemoveUnusedBuildings, PATCH_JUMP);
+ InjectHook(0x4083D0, CStreaming::RemoveBuildings, PATCH_JUMP);
+ InjectHook(0x408640, CStreaming::RemoveUnusedBigBuildings, PATCH_JUMP);
+ InjectHook(0x408680, CStreaming::RemoveBigBuildings, PATCH_JUMP);
+ InjectHook(0x408780, CStreaming::RemoveIslandsNotUsed, PATCH_JUMP);
+ InjectHook(0x40B180, CStreaming::RemoveLoadedVehicle, PATCH_JUMP);
+ InjectHook(0x4089B0, CStreaming::RemoveLeastUsedModel, PATCH_JUMP);
+ InjectHook(0x408940, CStreaming::RemoveAllUnusedModels, PATCH_JUMP);
+ InjectHook(0x409450, CStreaming::RemoveReferencedTxds, PATCH_JUMP);
+
+ InjectHook(0x40B160, CStreaming::GetAvailableVehicleSlot, PATCH_JUMP);
+ InjectHook(0x40B060, CStreaming::AddToLoadedVehiclesList, PATCH_JUMP);
+ InjectHook(0x4094C0, CStreaming::IsTxdUsedByRequestedModels, PATCH_JUMP);
+ InjectHook(0x407E70, CStreaming::IsObjectInCdImage, PATCH_JUMP);
+ InjectHook(0x408280, CStreaming::HaveAllBigBuildingsLoaded, PATCH_JUMP);
+ InjectHook(0x40A790, CStreaming::SetModelIsDeletable, PATCH_JUMP);
+ InjectHook(0x40A800, CStreaming::SetModelTxdIsDeletable, PATCH_JUMP);
+ InjectHook(0x40A820, CStreaming::SetMissionDoesntRequireModel, PATCH_JUMP);
+
+ InjectHook(0x40AA00, CStreaming::LoadInitialPeds, PATCH_JUMP);
+ InjectHook(0x40ADF0, CStreaming::LoadInitialVehicles, PATCH_JUMP);
+ InjectHook(0x40AE60, CStreaming::StreamVehiclesAndPeds, PATCH_JUMP);
+ InjectHook(0x40AA30, CStreaming::StreamZoneModels, PATCH_JUMP);
+ InjectHook(0x40AD00, CStreaming::RemoveCurrentZonesModels, PATCH_JUMP);
+
+ InjectHook(0x409BE0, CStreaming::ProcessLoadingChannel, PATCH_JUMP);
+ InjectHook(0x40A610, CStreaming::FlushChannels, PATCH_JUMP);
+ InjectHook(0x40A680, CStreaming::FlushRequestList, PATCH_JUMP);
+ InjectHook(0x409FF0, CStreaming::GetCdImageOffset, PATCH_JUMP);
+ InjectHook(0x409E50, CStreaming::GetNextFileOnCd, PATCH_JUMP);
+ InjectHook(0x40A060, CStreaming::RequestModelStream, PATCH_JUMP);
+ InjectHook(0x4077F0, CStreaming::RetryLoadFile, PATCH_JUMP);
+ InjectHook(0x40A390, CStreaming::LoadRequestedModels, PATCH_JUMP);
+ InjectHook(0x40A440, CStreaming::LoadAllRequestedModels, PATCH_JUMP);
+
+ InjectHook(0x4078F0, CStreaming::AddModelsToRequestList, PATCH_JUMP);
+ InjectHook(0x407C50, (void (*)(CPtrList&,float,float,float,float,float,float))CStreaming::ProcessEntitiesInSectorList, PATCH_JUMP);
+ InjectHook(0x407DD0, (void (*)(CPtrList&))CStreaming::ProcessEntitiesInSectorList, PATCH_JUMP);
+
+ InjectHook(0x407070, CStreaming::DeleteFarAwayRwObjects, PATCH_JUMP);
+ InjectHook(0x407390, CStreaming::DeleteAllRwObjects, PATCH_JUMP);
+ InjectHook(0x407400, CStreaming::DeleteRwObjectsAfterDeath, PATCH_JUMP);
+ InjectHook(0x408A60, CStreaming::DeleteRwObjectsBehindCamera, PATCH_JUMP);
+ InjectHook(0x407560, CStreaming::DeleteRwObjectsInSectorList, PATCH_JUMP);
+ InjectHook(0x4075A0, CStreaming::DeleteRwObjectsInOverlapSectorList, PATCH_JUMP);
+ InjectHook(0x409340, CStreaming::DeleteRwObjectsBehindCameraInSectorList, PATCH_JUMP);
+ InjectHook(0x4093C0, CStreaming::DeleteRwObjectsNotInFrustumInSectorList, PATCH_JUMP);
+ InjectHook(0x409B70, CStreaming::MakeSpaceFor, PATCH_JUMP);
+ InjectHook(0x40A6D0, CStreaming::LoadScene, PATCH_JUMP);
+
+ InjectHook(0x40B210, CStreaming::MemoryCardSave, PATCH_JUMP);
+ InjectHook(0x40B250, CStreaming::MemoryCardLoad, PATCH_JUMP);
+
+ InjectHook(0x4063E0, &CStreamingInfo::GetCdPosnAndSize, PATCH_JUMP);
+ InjectHook(0x406410, &CStreamingInfo::SetCdPosnAndSize, PATCH_JUMP);
+ InjectHook(0x4063D0, &CStreamingInfo::GetCdSize, PATCH_JUMP);
+ InjectHook(0x406380, &CStreamingInfo::AddToList, PATCH_JUMP);
+ InjectHook(0x4063A0, &CStreamingInfo::RemoveFromList, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Streaming.h b/src/core/Streaming.h
new file mode 100644
index 00000000..212a6d71
--- /dev/null
+++ b/src/core/Streaming.h
@@ -0,0 +1,188 @@
+#pragma once
+
+#include "Game.h"
+
+enum {
+ STREAM_OFFSET_MODEL = 0,
+ STREAM_OFFSET_TXD = STREAM_OFFSET_MODEL+MODELINFOSIZE,
+ NUMSTREAMINFO = STREAM_OFFSET_TXD+TXDSTORESIZE
+};
+
+enum StreamFlags
+{
+ STREAMFLAGS_DONT_REMOVE = 0x01,
+ STREAMFLAGS_SCRIPTOWNED = 0x02,
+ STREAMFLAGS_DEPENDENCY = 0x04, // Is this right?
+ STREAMFLAGS_PRIORITY = 0x08,
+ STREAMFLAGS_NOFADE = 0x10,
+
+ // TODO: this isn't named well, maybe CANT_REMOVE?
+ STREAMFLAGS_NOT_IN_LIST = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED,
+ STREAMFLAGS_KEEP_IN_MEMORY = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED|STREAMFLAGS_DEPENDENCY,
+};
+
+enum StreamLoadState
+{
+ STREAMSTATE_NOTLOADED = 0,
+ STREAMSTATE_LOADED = 1,
+ STREAMSTATE_INQUEUE = 2,
+ STREAMSTATE_READING = 3, // channel is reading
+ STREAMSTATE_STARTED = 4, // first part loaded
+};
+
+enum ChannelState
+{
+ CHANNELSTATE_IDLE = 0,
+ CHANNELSTATE_READING = 1,
+ CHANNELSTATE_STARTED = 2,
+ CHANNELSTATE_ERROR = 3,
+};
+
+class CStreamingInfo
+{
+public:
+ CStreamingInfo *m_next;
+ CStreamingInfo *m_prev;
+ uint8 m_loadState;
+ uint8 m_flags;
+
+ int16 m_nextID;
+ uint32 m_position;
+ uint32 m_size;
+
+ bool GetCdPosnAndSize(uint32 &posn, uint32 &size);
+ void SetCdPosnAndSize(uint32 posn, uint32 size);
+ void AddToList(CStreamingInfo *link);
+ void RemoveFromList(void);
+ uint32 GetCdSize(void) { return m_size; }
+ bool IsPriority(void) { return !!(m_flags & STREAMFLAGS_PRIORITY); }
+};
+
+struct CStreamingChannel
+{
+ int32 streamIds[4];
+ int32 offsets[4];
+ int32 state;
+ int32 field24;
+ int32 position;
+ int32 size;
+ int32 numTries;
+ int32 status; // from CdStream
+};
+
+class CDirectory;
+enum eLevelName;
+class CPtrList;
+
+class CStreaming
+{
+public:
+ static bool &ms_disableStreaming;
+ static bool &ms_bLoadingBigModel;
+ static int32 &ms_numModelsRequested;
+ static CStreamingInfo *ms_aInfoForModel; //[NUMSTREAMINFO]
+ static CStreamingInfo &ms_startLoadedList;
+ static CStreamingInfo &ms_endLoadedList;
+ static CStreamingInfo &ms_startRequestedList;
+ static CStreamingInfo &ms_endRequestedList;
+ static int32 &ms_oldSectorX;
+ static int32 &ms_oldSectorY;
+ static int32 &ms_streamingBufferSize;
+ static int8 **ms_pStreamingBuffer; //[2]
+ static int32 &ms_memoryUsed;
+ static CStreamingChannel *ms_channel; //[2]
+ static int32 &ms_channelError;
+ static int32 &ms_numVehiclesLoaded;
+ static int32 *ms_vehiclesLoaded; //[MAXVEHICLESLOADED]
+ static int32 &ms_lastVehicleDeleted;
+ static CDirectory *&ms_pExtraObjectsDir;
+ static int32 &ms_numPriorityRequests;
+ static bool &ms_hasLoadedLODs;
+ static int32 &ms_currentPedGrp;
+ static int32 ms_lastCullZone;
+ static uint16 &ms_loadedGangs;
+ static uint16 &ms_loadedGangCars;
+ static int32 ms_currentPedLoading;
+ static int32 *ms_imageOffsets; //[NUMCDIMAGES]
+ static int32 &ms_lastImageRead;
+ static int32 &ms_imageSize;
+ static uint32 &ms_memoryAvailable;
+
+ static void Init(void);
+ static void Shutdown(void);
+ static void Update(void);
+ static void LoadCdDirectory(void);
+ static void LoadCdDirectory(const char *dirname, int32 n);
+ static bool ConvertBufferToObject(int8 *buf, int32 streamId);
+ static bool FinishLoadingLargeFile(int8 *buf, int32 streamId);
+ static bool HasModelLoaded(int32 id) { return ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED; }
+ static void RequestModel(int32 model, int32 flags);
+ static void ReRequestModel(int32 model) { RequestModel(model, ms_aInfoForModel[model].m_flags); }
+ static void RequestTxd(int32 txd, int32 flags) { RequestModel(txd + STREAM_OFFSET_TXD, flags); }
+ static void ReRequestTxd(int32 txd) { ReRequestModel(txd + STREAM_OFFSET_TXD); }
+ static void RequestSubway(void);
+ static void RequestBigBuildings(eLevelName level);
+ static void RequestIslands(eLevelName level);
+ static void RequestSpecialModel(int32 modelId, const char *modelName, int32 flags);
+ static void RequestSpecialChar(int32 charId, const char *modelName, int32 flags);
+ static bool HasSpecialCharLoaded(int32 id);
+ static void SetMissionDoesntRequireSpecialChar(int32 id);
+ static void DecrementRef(int32 id);
+ static void RemoveModel(int32 id);
+ static void RemoveTxd(int32 id) { RemoveModel(id + STREAM_OFFSET_TXD); }
+ static void RemoveUnusedBuildings(eLevelName level);
+ static void RemoveBuildings(eLevelName level);
+ static void RemoveUnusedBigBuildings(eLevelName level);
+ static void RemoveIslandsNotUsed(eLevelName level);
+ static void RemoveBigBuildings(eLevelName level);
+ static bool RemoveLoadedVehicle(void);
+ static bool RemoveLeastUsedModel(void);
+ static void RemoveAllUnusedModels(void);
+ static void RemoveUnusedModelsInLoadedList(void);
+ static bool RemoveReferencedTxds(int32 mem);
+ static int32 GetAvailableVehicleSlot(void);
+ static bool IsTxdUsedByRequestedModels(int32 txdId);
+ static bool AddToLoadedVehiclesList(int32 modelId);
+ static bool IsObjectInCdImage(int32 id);
+ static void HaveAllBigBuildingsLoaded(eLevelName level);
+ static void SetModelIsDeletable(int32 id);
+ static void SetModelTxdIsDeletable(int32 id);
+ static void SetMissionDoesntRequireModel(int32 id);
+ static void LoadInitialPeds(void);
+ static void LoadInitialVehicles(void);
+ static void StreamVehiclesAndPeds(void);
+ static void StreamZoneModels(const CVector &pos);
+ static void RemoveCurrentZonesModels(void);
+
+ static int32 GetCdImageOffset(int32 lastPosn);
+ static int32 GetNextFileOnCd(int32 position, bool priority);
+ static void RequestModelStream(int32 ch);
+ static bool ProcessLoadingChannel(int32 ch);
+ static void RetryLoadFile(int32 ch);
+ static void LoadRequestedModels(void);
+ static void LoadAllRequestedModels(bool priority);
+ static void FlushChannels(void);
+ static void FlushRequestList(void);
+
+ static void MakeSpaceFor(int32 size);
+ static void ImGonnaUseStreamingMemory(void);
+ static void IHaveUsedStreamingMemory(void);
+ static void UpdateMemoryUsed(void);
+
+ static void AddModelsToRequestList(const CVector &pos);
+ static void ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax);
+ static void ProcessEntitiesInSectorList(CPtrList &list);
+ static void DeleteFarAwayRwObjects(const CVector &pos);
+ static void DeleteAllRwObjects(void);
+ static void DeleteRwObjectsAfterDeath(const CVector &pos);
+ static void DeleteRwObjectsBehindCamera(int32 mem);
+ static void DeleteRwObjectsInSectorList(CPtrList &list);
+ static void DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y);
+ static bool DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, int32 mem);
+ static bool DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, int32 mem);
+
+ static void LoadScene(const CVector &pos);
+
+ static void MemoryCardSave(uint8 *buffer, uint32 *length);
+ static void MemoryCardLoad(uint8 *buffer, uint32 length);
+};
diff --git a/src/core/SurfaceTable.cpp b/src/core/SurfaceTable.cpp
new file mode 100644
index 00000000..2ba884b1
--- /dev/null
+++ b/src/core/SurfaceTable.cpp
@@ -0,0 +1,150 @@
+#include "common.h"
+#include "patcher.h"
+#include "main.h"
+#include "FileMgr.h"
+#include "Weather.h"
+#include "Collision.h"
+#include "SurfaceTable.h"
+
+float (*CSurfaceTable::ms_aAdhesiveLimitTable)[NUMADHESIVEGROUPS] = (float (*)[NUMADHESIVEGROUPS])0x8E29D4;
+
+void
+CSurfaceTable::Initialise(char *filename)
+{
+ int lineno, fieldno;
+ char *line;
+ char surfname[256];
+ float adhesiveLimit;
+
+ CFileMgr::SetDir("");
+ CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
+
+ line = (char*)work_buff;
+ for(lineno = 0; lineno < NUMADHESIVEGROUPS; lineno++){
+ // skip white space and comments
+ while(*line == ' ' || *line == '\t' || *line == '\n' || *line == '\r' || *line == ';'){
+ if(*line == ';'){
+ while(*line != '\n' && *line != '\r')
+ line++;
+ }else
+ line++;
+ }
+
+ sscanf(line, "%s", surfname);
+ // skip what we just read
+ while(!(*line == ' ' || *line == '\t' || *line == ','))
+ line++;
+
+ for(fieldno = 0; fieldno <= lineno; fieldno++){
+ // skip white space
+ while(*line == ' ' || *line == '\t' || *line == ',')
+ line++;
+ adhesiveLimit = 0.0f;
+ if(*line != '-')
+ sscanf(line, "%f", &adhesiveLimit);
+ // skip what we just read
+ while(!(*line == ' ' || *line == '\t' || *line == ',' || *line == '\n'))
+ line++;
+
+ ms_aAdhesiveLimitTable[lineno][fieldno] = adhesiveLimit;
+ ms_aAdhesiveLimitTable[fieldno][lineno] = adhesiveLimit;
+ }
+ }
+}
+
+int
+CSurfaceTable::GetAdhesionGroup(uint8 surfaceType)
+{
+ switch(surfaceType){
+ case SURFACE_0: return ADHESIVE_ROAD;
+ case SURFACE_1: return ADHESIVE_ROAD;
+ case SURFACE_2: return ADHESIVE_LOOSE;
+ case SURFACE_3: return ADHESIVE_LOOSE;
+ case SURFACE_4: return ADHESIVE_HARD;
+ case SURFACE_5: return ADHESIVE_ROAD;
+ case SURFACE_6: return ADHESIVE_HARD;
+ case SURFACE_7: return ADHESIVE_HARD;
+ case SURFACE_8: return ADHESIVE_HARD;
+ case SURFACE_9: return ADHESIVE_HARD;
+ case SURFACE_10: return ADHESIVE_HARD;
+ case SURFACE_11: return ADHESIVE_HARD;
+ case SURFACE_12: return ADHESIVE_HARD;
+ case SURFACE_13: return ADHESIVE_HARD;
+ case SURFACE_14: return ADHESIVE_HARD;
+ case SURFACE_15: return ADHESIVE_HARD;
+ case SURFACE_16: return ADHESIVE_HARD;
+ case SURFACE_17: return ADHESIVE_RUBBER;
+ case SURFACE_18: return ADHESIVE_LOOSE;
+ case SURFACE_19: return ADHESIVE_WET;
+ case SURFACE_20: return ADHESIVE_ROAD;
+ case SURFACE_21: return ADHESIVE_ROAD;
+ case SURFACE_22: return ADHESIVE_ROAD;
+ case SURFACE_23: return ADHESIVE_RUBBER;
+ case SURFACE_24: return ADHESIVE_HARD;
+ case SURFACE_25: return ADHESIVE_LOOSE;
+ case SURFACE_26: return ADHESIVE_LOOSE;
+ case SURFACE_27: return ADHESIVE_HARD;
+ case SURFACE_28: return ADHESIVE_HARD;
+ case SURFACE_29: return ADHESIVE_RUBBER;
+ case SURFACE_30: return ADHESIVE_LOOSE;
+ case SURFACE_31: return ADHESIVE_HARD;
+ case SURFACE_32: return ADHESIVE_HARD;
+ default: return ADHESIVE_ROAD;
+ }
+}
+
+float
+CSurfaceTable::GetWetMultiplier(uint8 surfaceType)
+{
+ switch(surfaceType){
+ case SURFACE_0:
+ case SURFACE_1:
+ case SURFACE_4:
+ case SURFACE_5:
+ case SURFACE_8:
+ case SURFACE_20:
+ case SURFACE_21:
+ case SURFACE_22:
+ case SURFACE_25:
+ case SURFACE_30:
+ case SURFACE_31:
+ return 1.0f - CWeather::WetRoads*0.25f;
+
+ case SURFACE_2:
+ case SURFACE_6:
+ case SURFACE_7:
+ case SURFACE_9:
+ case SURFACE_10:
+ case SURFACE_11:
+ case SURFACE_12:
+ case SURFACE_13:
+ case SURFACE_14:
+ case SURFACE_15:
+ case SURFACE_16:
+ case SURFACE_17:
+ case SURFACE_23:
+ case SURFACE_24:
+ case SURFACE_26:
+ case SURFACE_27:
+ case SURFACE_28:
+ case SURFACE_29:
+ case SURFACE_32:
+ return 1.0f - CWeather::WetRoads*0.4f;
+
+ default:
+ return 1.0f;
+ }
+}
+
+float
+CSurfaceTable::GetAdhesiveLimit(CColPoint &colpoint)
+{
+ return ms_aAdhesiveLimitTable[GetAdhesionGroup(colpoint.surfaceB)][GetAdhesionGroup(colpoint.surfaceA)];
+}
+
+STARTPATCHES
+ InjectHook(0x4AB8F0, CSurfaceTable::Initialise, PATCH_JUMP);
+ InjectHook(0x4ABA60, CSurfaceTable::GetAdhesionGroup, PATCH_JUMP);
+ InjectHook(0x4ABAA0, CSurfaceTable::GetWetMultiplier, PATCH_JUMP);
+ InjectHook(0x4ABA30, CSurfaceTable::GetAdhesiveLimit, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/SurfaceTable.h b/src/core/SurfaceTable.h
new file mode 100644
index 00000000..e1882e69
--- /dev/null
+++ b/src/core/SurfaceTable.h
@@ -0,0 +1,106 @@
+#pragma once
+
+
+enum
+{
+ SURFACE_0,
+ SURFACE_1,
+ SURFACE_2,
+ SURFACE_3,
+ SURFACE_4,
+ SURFACE_5,
+ SURFACE_6,
+ SURFACE_7,
+ SURFACE_8,
+ SURFACE_9,
+ SURFACE_10,
+ SURFACE_11,
+ SURFACE_12,
+ SURFACE_13,
+ SURFACE_14,
+ SURFACE_15,
+ SURFACE_16,
+ SURFACE_17,
+ SURFACE_18,
+ SURFACE_19,
+ SURFACE_20,
+ SURFACE_21,
+ SURFACE_22,
+ SURFACE_23,
+ SURFACE_24,
+ SURFACE_25,
+ SURFACE_26,
+ SURFACE_27,
+ SURFACE_28,
+ SURFACE_29,
+ SURFACE_30,
+ SURFACE_31,
+ SURFACE_32,
+
+ NUMSURFACETYPES
+};
+
+// From nick
+// TODO: check and use this
+enum eSurfaceType
+{
+ SURFACE_DEFAULT,
+ SURFACE_TARMAC,
+ SURFACE_GRASS,
+ SURFACE_DIRT,
+ SURFACE_DIRTTRACK,
+ SURFACE_PAVEMENT,
+ SURFACE_METAL6,
+ SURFACE_GLASS,
+ SURFACE_SCAFFOLD,
+ SURFACE_METAL_DOOR, // garage door
+ SURFACE_BILLBOARD,
+ SURFACE_STEEL, //?
+ SURFACE_METAL_POLE, // ?
+ SURFACE_STREET_LIGHT,
+ SURFACE_METAL14,
+ SURFACE_METAL15,
+ SURFACE_METAL_FENCE,
+ SURFACE_FLESH,
+ SURFACE_SAND,
+ SURFACE_PUDDLE,
+ SURFACE_WOOD,
+ SURFACE_WOOD_BOX,
+ SURFACE_WOOD_PLANK,
+ SURFACE_TIRE,
+ SURFACE_HARD24,
+ SURFACE_HEDGE,
+ SURFACE_STONE,
+ SURFACE_METAL27,
+ SURFACE_METAL28,
+ SURFACE_RUBBER29,
+ SURFACE_LOOSE30,
+ SURFACE_BOLLARD,
+ SURFACE_GATE,
+ SURFACE_SAND33,
+ SURFACE_ROAD34,
+};
+
+enum
+{
+ ADHESIVE_RUBBER,
+ ADHESIVE_HARD,
+ ADHESIVE_ROAD,
+ ADHESIVE_LOOSE,
+ ADHESIVE_WET,
+
+ NUMADHESIVEGROUPS
+};
+
+struct CColPoint;
+
+class CSurfaceTable
+{
+// static float ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS];
+ static float (*ms_aAdhesiveLimitTable)[NUMADHESIVEGROUPS];
+public:
+ static void Initialise(char *filename);
+ static int GetAdhesionGroup(uint8 surfaceType);
+ static float GetWetMultiplier(uint8 surfaceType);
+ static float GetAdhesiveLimit(CColPoint &colpoint);
+};
diff --git a/src/core/TempColModels.cpp b/src/core/TempColModels.cpp
new file mode 100644
index 00000000..a323d7c9
--- /dev/null
+++ b/src/core/TempColModels.cpp
@@ -0,0 +1,17 @@
+#include "common.h"
+#include "patcher.h"
+#include "TempColModels.h"
+
+CColModel &CTempColModels::ms_colModelPed1 = *(CColModel*)0x726CB0;
+CColModel &CTempColModels::ms_colModelPed2 = *(CColModel*)0x726D08;
+CColModel &CTempColModels::ms_colModelBBox = *(CColModel*)0x727FE0;
+CColModel &CTempColModels::ms_colModelBumper1 = *(CColModel*)0x86BE88;
+CColModel &CTempColModels::ms_colModelWheel1 = *(CColModel*)0x878C40;
+CColModel &CTempColModels::ms_colModelPanel1 = *(CColModel*)0x87BDD8;
+CColModel &CTempColModels::ms_colModelBodyPart2 = *(CColModel*)0x87BE30;
+CColModel &CTempColModels::ms_colModelBodyPart1 = *(CColModel*)0x87BE88;
+CColModel &CTempColModels::ms_colModelCutObj = *(CColModel*)0x87C960;
+CColModel &CTempColModels::ms_colModelPedGroundHit = *(CColModel*)0x880480;
+CColModel &CTempColModels::ms_colModelBoot1 = *(CColModel*)0x880670;
+CColModel &CTempColModels::ms_colModelDoor1 = *(CColModel*)0x880850;
+CColModel &CTempColModels::ms_colModelBonnet1 = *(CColModel*)0x8808A8;
diff --git a/src/core/TempColModels.h b/src/core/TempColModels.h
new file mode 100644
index 00000000..8ac74428
--- /dev/null
+++ b/src/core/TempColModels.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Collision.h"
+
+class CTempColModels
+{
+public:
+ static CColModel &ms_colModelPed1;
+ static CColModel &ms_colModelPed2;
+ static CColModel &ms_colModelBBox;
+ static CColModel &ms_colModelBumper1;
+ static CColModel &ms_colModelWheel1;
+ static CColModel &ms_colModelPanel1;
+ static CColModel &ms_colModelBodyPart2;
+ static CColModel &ms_colModelBodyPart1;
+ static CColModel &ms_colModelCutObj;
+ static CColModel &ms_colModelPedGroundHit;
+ static CColModel &ms_colModelBoot1;
+ static CColModel &ms_colModelDoor1;
+ static CColModel &ms_colModelBonnet1;
+};
diff --git a/src/core/Text.cpp b/src/core/Text.cpp
new file mode 100644
index 00000000..d7d63467
--- /dev/null
+++ b/src/core/Text.cpp
@@ -0,0 +1,232 @@
+#include "common.h"
+#include "patcher.h"
+#include "FileMgr.h"
+#include "Frontend.h"
+#include "Messages.h"
+#include "Text.h"
+
+static wchar WideErrorString[25];
+
+CText &TheText = *(CText*)0x941520;
+
+CText::CText(void)
+{
+ keyArray.entries = nil;
+ keyArray.numEntries = 0;
+ data.chars = nil;
+ data.numChars = 0;
+ encoding = 101;
+ memset(WideErrorString, 0, sizeof(WideErrorString));
+}
+
+CText::~CText(void)
+{
+ data.Unload();
+ keyArray.Unload();
+}
+
+void
+CText::Load(void)
+{
+ uint8 *filedata;
+ char filename[32], type[4];
+ int length;
+ int offset, sectlen;
+
+ Unload();
+ filedata = new uint8[0x40000];
+
+ CFileMgr::SetDir("TEXT");
+ switch(CMenuManager::m_PrefsLanguage){
+ case LANGUAGE_AMERICAN:
+ sprintf(filename, "AMERICAN.GXT");
+ break;
+ case LANGUAGE_FRENCH:
+ sprintf(filename, "FRENCH.GXT");
+ break;
+ case LANGUAGE_GERMAN:
+ sprintf(filename, "GERMAN.GXT");
+ break;
+ case LANGUAGE_ITALIAN:
+ sprintf(filename, "ITALIAN.GXT");
+ break;
+ case LANGUAGE_SPANISH:
+ sprintf(filename, "SPANISH.GXT");
+ break;
+ }
+
+ length = CFileMgr::LoadFile(filename, filedata, 0x40000, "rb");
+ CFileMgr::SetDir("");
+
+ offset = 0;
+ while(offset < length){
+ type[0] = filedata[offset++];
+ type[1] = filedata[offset++];
+ type[2] = filedata[offset++];
+ type[3] = filedata[offset++];
+ sectlen = (int)filedata[offset+3]<<24 | (int)filedata[offset+2]<<16 |
+ (int)filedata[offset+1]<<8 | (int)filedata[offset+0];
+ offset += 4;
+ if(sectlen != 0){
+ if(strncmp(type, "TKEY", 4) == 0)
+ keyArray.Load(sectlen, filedata, &offset);
+ else if(strncmp(type, "TDAT", 4) == 0)
+ data.Load(sectlen, filedata, &offset);
+ else
+ offset += sectlen;
+ }
+ }
+
+ keyArray.Update(data.chars);
+
+ delete[] filedata;
+}
+
+void
+CText::Unload(void)
+{
+ CMessages::ClearAllMessagesDisplayedByGame();
+ data.Unload();
+ keyArray.Unload();
+}
+
+wchar*
+CText::Get(const char *key)
+{
+ return keyArray.Search(key);
+}
+
+wchar
+CText::GetUpperCase(wchar c)
+{
+ // TODO: do this depending on encoding
+ if(islower(c))
+ return toupper(c);
+ return c;
+}
+
+void
+CText::UpperCase(wchar *s)
+{
+ while(*s){
+ *s = GetUpperCase(*s);
+ s++;
+ }
+}
+
+
+void
+CKeyArray::Load(uint32 length, uint8 *data, int *offset)
+{
+ uint32 i;
+ uint8 *rawbytes;
+
+ numEntries = length / sizeof(CKeyEntry);
+ entries = new CKeyEntry[numEntries];
+ rawbytes = (uint8*)entries;
+
+ for(i = 0; i < length; i++)
+ rawbytes[i] = data[(*offset)++];
+}
+
+void
+CKeyArray::Unload(void)
+{
+ delete[] entries;
+ entries = nil;
+ numEntries = 0;
+}
+
+void
+CKeyArray::Update(wchar *chars)
+{
+ int i;
+ for(i = 0; i < numEntries; i++)
+ entries[i].value = (wchar*)((uint8*)chars + (uintptr)entries[i].value);
+}
+
+CKeyEntry*
+CKeyArray::BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high)
+{
+ int mid;
+ int diff;
+
+ if(low > high)
+ return nil;
+
+ mid = (low + high)/2;
+ diff = strcmp(key, entries[mid].key);
+ if(diff == 0)
+ return &entries[mid];
+ if(diff < 0)
+ return BinarySearch(key, entries, low, mid-1);
+ if(diff > 0)
+ return BinarySearch(key, entries, mid+1, high);
+ return nil;
+}
+
+wchar*
+CKeyArray::Search(const char *key)
+{
+ CKeyEntry *found;
+ char errstr[25];
+ int i;
+
+ found = BinarySearch(key, entries, 0, numEntries-1);
+ if(found)
+ return found->value;
+ sprintf(errstr, "%s missing", key);
+ for(i = 0; i < 25; i++)
+ WideErrorString[i] = errstr[i];
+ return WideErrorString;
+}
+
+
+void
+CData::Load(uint32 length, uint8 *data, int *offset)
+{
+ uint32 i;
+ uint8 *rawbytes;
+
+ numChars = length / sizeof(wchar);
+ chars = new wchar[numChars];
+ rawbytes = (uint8*)chars;
+
+ for(i = 0; i < length; i++)
+ rawbytes[i] = data[(*offset)++];
+}
+
+void
+CData::Unload(void)
+{
+ delete[] chars;
+ chars = nil;
+ numChars = 0;
+}
+
+void
+AsciiToUnicode(const char *src, uint16 *dst)
+{
+ while((*dst++ = *src++) != '\0');
+}
+
+void
+TextCopy(wchar *dst, const wchar *src)
+{
+ while((*dst++ = *src++) != '\0');
+}
+
+STARTPATCHES
+ InjectHook(0x52C3C0, &CText::Load, PATCH_JUMP);
+ InjectHook(0x52C580, &CText::Unload, PATCH_JUMP);
+ InjectHook(0x52C5A0, &CText::Get, PATCH_JUMP);
+
+ InjectHook(0x52BE70, &CKeyArray::Load, PATCH_JUMP);
+ InjectHook(0x52BF60, &CKeyArray::Unload, PATCH_JUMP);
+ InjectHook(0x52BF80, &CKeyArray::Update, PATCH_JUMP);
+ InjectHook(0x52C060, &CKeyArray::BinarySearch, PATCH_JUMP);
+ InjectHook(0x52BFB0, &CKeyArray::Search, PATCH_JUMP);
+
+ InjectHook(0x52C120, &CData::Load, PATCH_JUMP);
+ InjectHook(0x52C200, &CData::Unload, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Text.h b/src/core/Text.h
new file mode 100644
index 00000000..2592e6b8
--- /dev/null
+++ b/src/core/Text.h
@@ -0,0 +1,52 @@
+#pragma once
+
+void AsciiToUnicode(const char *src, wchar *dst);
+void TextCopy(wchar *dst, const wchar *src);
+
+struct CKeyEntry
+{
+ wchar *value;
+ char key[8];
+};
+// If this fails, CKeyArray::Load will have to be fixed
+static_assert(sizeof(CKeyEntry) == 12, "CKeyEntry: error");
+
+class CKeyArray
+{
+public:
+ CKeyEntry *entries;
+ int numEntries;
+
+ void Load(uint32 length, uint8 *data, int *offset);
+ void Unload(void);
+ void Update(wchar *chars);
+ CKeyEntry *BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high);
+ wchar *Search(const char *key);
+};
+
+class CData
+{
+public:
+ wchar *chars;
+ int numChars;
+
+ void Load(uint32 length, uint8 *data, int *offset);
+ void Unload(void);
+};
+
+class CText
+{
+ CKeyArray keyArray;
+ CData data;
+ int8 encoding;
+public:
+ CText(void);
+ ~CText(void);
+ void Load(void);
+ void Unload(void);
+ wchar *Get(const char *key);
+ wchar GetUpperCase(wchar c);
+ void UpperCase(wchar *s);
+};
+
+extern CText &TheText;
diff --git a/src/core/Timer.cpp b/src/core/Timer.cpp
new file mode 100644
index 00000000..543f582b
--- /dev/null
+++ b/src/core/Timer.cpp
@@ -0,0 +1,235 @@
+#include <windows.h>
+#include "common.h"
+#include "patcher.h"
+#include "DMAudio.h"
+#include "Record.h"
+#include "Timer.h"
+
+uint32 &CTimer::m_snTimeInMilliseconds = *(uint32*)0x885B48;
+uint32 &CTimer::m_snTimeInMillisecondsPauseMode = *(uint32*)0x5F7614;
+uint32 &CTimer::m_snTimeInMillisecondsNonClipped = *(uint32*)0x9412E8;
+uint32 &CTimer::m_snPreviousTimeInMilliseconds = *(uint32*)0x8F29E4;
+uint32 &CTimer::m_FrameCounter = *(uint32*)0x9412EC;
+float &CTimer::ms_fTimeScale = *(float*)0x8F2C20;
+float &CTimer::ms_fTimeStep = *(float*)0x8E2CB4;
+float &CTimer::ms_fTimeStepNonClipped = *(float*)0x8E2C4C;
+bool &CTimer::m_UserPause = *(bool*)0x95CD7C;
+bool &CTimer::m_CodePause = *(bool*)0x95CDB1;
+
+//UInt32 oldPcTimer;
+uint32 &oldPcTimer = *(uint32*)0x9434F4;
+
+//UInt32 suspendPcTimer;
+uint32 &suspendPcTimer = *(uint32*)0x62A308;
+
+//UInt32 _nCyclesPerMS = 1;
+uint32 &_nCyclesPerMS = *(uint32*)0x5F7610;
+
+//LARGE_INTEGER _oldPerfCounter;
+LARGE_INTEGER &_oldPerfCounter = *(LARGE_INTEGER*)0x62A310;
+
+//LARGE_INTEGER perfSuspendCounter;
+LARGE_INTEGER &perfSuspendCounter = *(LARGE_INTEGER*)0x62A318;
+
+//UInt32 suspendDepth;
+uint32 &suspendDepth = *(uint32*)0x62A320;
+
+void CTimer::Initialise(void)
+{
+ debug("Initialising CTimer...\n");
+
+ ms_fTimeScale = 1.0f;
+ ms_fTimeStep = 1.0f;
+ suspendDepth = 0;
+ m_UserPause = false;
+ m_CodePause = false;
+ m_snTimeInMillisecondsNonClipped = 0;
+ m_snPreviousTimeInMilliseconds = 0;
+ m_snTimeInMilliseconds = 1;
+
+ LARGE_INTEGER perfFreq;
+ if ( QueryPerformanceFrequency(&perfFreq) )
+ {
+ OutputDebugString("Performance counter available\n");
+ _nCyclesPerMS = uint32(perfFreq.QuadPart / 1000);
+ QueryPerformanceCounter(&_oldPerfCounter);
+ }
+ else
+ {
+ OutputDebugString("Performance counter not available, using millesecond timer\n");
+ _nCyclesPerMS = 0;
+ oldPcTimer = RsTimer();
+ }
+
+ m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds;
+
+ m_FrameCounter = 0;
+
+ DMAudio.ResetTimers(m_snPreviousTimeInMilliseconds);
+
+ debug("CTimer ready\n");
+}
+
+void CTimer::Shutdown(void)
+{
+ ;
+}
+
+#if 1
+WRAPPER void CTimer::Update(void) { EAXJMP(0x4ACF70); }
+#else
+void CTimer::Update(void)
+{
+ m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds;
+
+ if ( (double)_nCyclesPerMS != 0.0 )
+ {
+ LARGE_INTEGER pc;
+ QueryPerformanceCounter(&pc);
+
+ int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart) & 0x7FFFFFFF;
+
+ _oldPerfCounter = pc;
+
+ double updInCyclesScaled = (double)updInCycles * ms_fTimeScale;
+
+ double upd = updInCyclesScaled / (double)_nCyclesPerMS;
+
+ m_snTimeInMillisecondsPauseMode = (Int64)(m_snTimeInMillisecondsPauseMode + upd);
+
+ if ( GetIsPaused() )
+ ms_fTimeStep = 0.0f;
+ else
+ {
+ m_snTimeInMilliseconds = (Int64)(m_snTimeInMilliseconds + upd);
+ m_snTimeInMillisecondsNonClipped = (Int64)(m_snTimeInMillisecondsNonClipped + upd);
+ ms_fTimeStep = updInCyclesScaled / (double)_nCyclesPerMS / 20.0;
+ }
+ }
+ else
+ {
+ uint32 timer = RsTimer();
+
+ uint32 updInMs = timer - oldPcTimer;
+
+ double upd = (double)updInMs * ms_fTimeScale;
+
+ oldPcTimer = timer;
+
+ m_snTimeInMillisecondsPauseMode = (Int64)(m_snTimeInMillisecondsPauseMode + upd);
+
+ if ( GetIsPaused() )
+ ms_fTimeStep = 0.0f;
+ else
+ {
+ m_snTimeInMilliseconds = (Int64)(m_snTimeInMilliseconds + upd);
+ m_snTimeInMillisecondsNonClipped = (Int64)(m_snTimeInMillisecondsNonClipped + upd);
+ ms_fTimeStep = upd / 1000.0f * 50.0f;
+ }
+ }
+
+ if ( ms_fTimeStep < 0.01f && !GetIsPaused() )
+ ms_fTimeStep = 0.01f;
+
+ ms_fTimeStepNonClipped = ms_fTimeStep;
+
+ if ( CRecordDataForGame::RecordingState != RECORDSTATE_2 )
+ {
+ ms_fTimeStep = min(3.0f, ms_fTimeStep);
+
+ if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 )
+ m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60;
+ }
+
+ if ( CRecordDataForChase::Status == RECORDSTATE_1 )
+ {
+ ms_fTimeStep = 1.0f;
+ m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16;
+ }
+
+ m_FrameCounter++;
+}
+#endif
+
+void CTimer::Suspend(void)
+{
+ if ( ++suspendDepth > 1 )
+ return;
+
+ if ( (double)_nCyclesPerMS != 0.0 )
+ QueryPerformanceCounter(&perfSuspendCounter);
+ else
+ suspendPcTimer = RsTimer();
+}
+
+void CTimer::Resume(void)
+{
+ if ( --suspendDepth != 0 )
+ return;
+
+ if ( (double)_nCyclesPerMS != 0.0 )
+ {
+ LARGE_INTEGER pc;
+ QueryPerformanceCounter(&pc);
+
+ _oldPerfCounter.LowPart += pc.LowPart - perfSuspendCounter.LowPart;
+ }
+ else
+ oldPcTimer += RsTimer() - suspendPcTimer;
+}
+
+uint32 CTimer::GetCyclesPerMillisecond(void)
+{
+ if (_nCyclesPerMS != 0)
+ return _nCyclesPerMS;
+ else
+ return 1;
+}
+
+uint32 CTimer::GetCurrentTimeInCycles(void)
+{
+ if ( _nCyclesPerMS != 0 )
+ {
+ LARGE_INTEGER pc;
+ QueryPerformanceCounter(&pc);
+ return (pc.LowPart - _oldPerfCounter.LowPart) & 0x7FFFFFFF;
+ }
+ else
+ return RsTimer() - oldPcTimer;
+}
+
+bool CTimer::GetIsSlowMotionActive(void)
+{
+ return ms_fTimeScale < 1.0f;
+}
+
+void CTimer::Stop(void)
+{
+ m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds;
+}
+
+void CTimer::StartUserPause(void)
+{
+ m_UserPause = true;
+}
+
+void CTimer::EndUserPause(void)
+{
+ m_UserPause = false;
+}
+
+#if 0
+STARTPATCHES
+ InjectHook(0x4ACE60, CTimer::Initialise, PATCH_JUMP);
+ InjectHook(0x4ACF60, CTimer::Shutdown, PATCH_JUMP);
+ InjectHook(0x4ACF70, CTimer::Update, PATCH_JUMP);
+ InjectHook(0x4AD310, CTimer::Suspend, PATCH_JUMP);
+ InjectHook(0x4AD370, CTimer::Resume, PATCH_JUMP);
+ InjectHook(0x4AD3F0, CTimer::GetCyclesPerMillisecond, PATCH_JUMP);
+ InjectHook(0x4AD410, CTimer::GetCurrentTimeInCycles, PATCH_JUMP);
+ InjectHook(0x4AD450, CTimer::GetIsSlowMotionActive, PATCH_JUMP);
+ InjectHook(0x4AD480, CTimer::Stop, PATCH_JUMP);
+ InjectHook(0x4AD490, CTimer::StartUserPause, PATCH_JUMP);
+ InjectHook(0x4AD4A0, CTimer::EndUserPause, PATCH_JUMP);
+ENDPATCHES
+#endif
diff --git a/src/core/Timer.h b/src/core/Timer.h
new file mode 100644
index 00000000..9e6d447e
--- /dev/null
+++ b/src/core/Timer.h
@@ -0,0 +1,51 @@
+#pragma once
+
+class CTimer
+{
+ static uint32 &m_snTimeInMilliseconds;
+ static uint32 &m_snTimeInMillisecondsPauseMode;
+ static uint32 &m_snTimeInMillisecondsNonClipped;
+ static uint32 &m_snPreviousTimeInMilliseconds;
+ static uint32 &m_FrameCounter;
+ static float &ms_fTimeScale;
+ static float &ms_fTimeStep;
+ static float &ms_fTimeStepNonClipped;
+ static bool &m_UserPause;
+ static bool &m_CodePause;
+public:
+ static float GetTimeStep(void) { return ms_fTimeStep; }
+ static void SetTimeStep(float ts) { ms_fTimeStep = ts; }
+ static float GetTimeStepInSeconds() { return ms_fTimeStep / 50.0f; }
+ static float GetTimeStepInMilliseconds() { return ms_fTimeStep / 50.0f * 1000.0f; }
+ static float GetTimeStepNonClipped(void) { return ms_fTimeStepNonClipped; }
+ static float GetTimeStepNonClippedInSeconds(void) { return ms_fTimeStepNonClipped / 50.0f; }
+ static void SetTimeStepNonClipped(float ts) { ms_fTimeStepNonClipped = ts; }
+ static uint32 GetFrameCounter(void) { return m_FrameCounter; }
+ static void SetFrameCounter(uint32 fc) { m_FrameCounter = fc; }
+ static uint32 GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; }
+ static void SetTimeInMilliseconds(uint32 t) { m_snTimeInMilliseconds = t; }
+ static uint32 GetTimeInMillisecondsNonClipped(void) { return m_snTimeInMillisecondsNonClipped; }
+ static void SetTimeInMillisecondsNonClipped(uint32 t) { m_snTimeInMillisecondsNonClipped = t; }
+ static uint32 GetTimeInMillisecondsPauseMode(void) { return m_snTimeInMillisecondsPauseMode; }
+ static void SetTimeInMillisecondsPauseMode(uint32 t) { m_snTimeInMillisecondsPauseMode = t; }
+ static uint32 GetPreviousTimeInMilliseconds(void) { return m_snPreviousTimeInMilliseconds; }
+ static void SetPreviousTimeInMilliseconds(uint32 t) { m_snPreviousTimeInMilliseconds = t; }
+ static float GetTimeScale(void) { return ms_fTimeScale; }
+ static void SetTimeScale(float ts) { ms_fTimeScale = ts; }
+
+ static bool GetIsPaused() { return m_UserPause || m_CodePause; }
+ static bool GetIsUserPaused() { return m_UserPause; }
+ static void SetCodePause(bool pause) { m_CodePause = pause; }
+
+ static void Initialise(void);
+ static void Shutdown(void);
+ static void Update(void);
+ static void Suspend(void);
+ static void Resume(void);
+ static uint32 GetCyclesPerMillisecond(void);
+ static uint32 GetCurrentTimeInCycles(void);
+ static bool GetIsSlowMotionActive(void);
+ static void Stop(void);
+ static void StartUserPause(void);
+ static void EndUserPause(void);
+};
diff --git a/src/core/TxdStore.cpp b/src/core/TxdStore.cpp
new file mode 100644
index 00000000..5085c7e4
--- /dev/null
+++ b/src/core/TxdStore.cpp
@@ -0,0 +1,208 @@
+#include "common.h"
+#include "patcher.h"
+#include "templates.h"
+#include "Streaming.h"
+#include "RwHelper.h"
+#include "TxdStore.h"
+
+CPool<TxdDef,TxdDef> *&CTxdStore::ms_pTxdPool = *(CPool<TxdDef,TxdDef>**)0x8F5FB8;
+RwTexDictionary *&CTxdStore::ms_pStoredTxd = *(RwTexDictionary**)0x9405BC;
+
+void
+CTxdStore::Initialize(void)
+{
+ if(ms_pTxdPool == nil)
+ ms_pTxdPool = new CPool<TxdDef,TxdDef>(TXDSTORESIZE);
+}
+
+void
+CTxdStore::Shutdown(void)
+{
+ if(ms_pTxdPool)
+ delete ms_pTxdPool;
+}
+
+void
+CTxdStore::GameShutdown(void)
+{
+ int i;
+
+ for(i = 0; i < TXDSTORESIZE; i++){
+ TxdDef *def = GetSlot(i);
+ if(def && GetNumRefs(i) == 0)
+ RemoveTxdSlot(i);
+ }
+}
+
+int
+CTxdStore::AddTxdSlot(const char *name)
+{
+ TxdDef *def = ms_pTxdPool->New();
+ assert(def);
+ def->texDict = nil;
+ def->refCount = 0;
+ strcpy(def->name, name);
+ return ms_pTxdPool->GetJustIndex(def);
+}
+
+void
+CTxdStore::RemoveTxdSlot(int slot)
+{
+ TxdDef *def = GetSlot(slot);
+ if(def->texDict)
+ RwTexDictionaryDestroy(def->texDict);
+ ms_pTxdPool->Delete(def);
+}
+
+int
+CTxdStore::FindTxdSlot(const char *name)
+{
+ char *defname;
+ int size = ms_pTxdPool->GetSize();
+ for(int i = 0; i < size; i++){
+ defname = GetTxdName(i);
+ if(defname && _strcmpi(defname, name) == 0)
+ return i;
+ }
+ return -1;
+}
+
+char*
+CTxdStore::GetTxdName(int slot)
+{
+ TxdDef *def = GetSlot(slot);
+ return def ? def->name : nil;
+}
+
+void
+CTxdStore::PushCurrentTxd(void)
+{
+ ms_pStoredTxd = RwTexDictionaryGetCurrent();
+}
+
+void
+CTxdStore::PopCurrentTxd(void)
+{
+ RwTexDictionarySetCurrent(ms_pStoredTxd);
+ ms_pStoredTxd = nil;
+}
+
+void
+CTxdStore::SetCurrentTxd(int slot)
+{
+ TxdDef *def = GetSlot(slot);
+ if(def)
+ RwTexDictionarySetCurrent(def->texDict);
+}
+
+void
+CTxdStore::Create(int slot)
+{
+ GetSlot(slot)->texDict = RwTexDictionaryCreate();
+}
+
+int
+CTxdStore::GetNumRefs(int slot)
+{
+ return GetSlot(slot)->refCount;
+}
+
+void
+CTxdStore::AddRef(int slot)
+{
+ GetSlot(slot)->refCount++;
+}
+
+void
+CTxdStore::RemoveRef(int slot)
+{
+ if(--GetSlot(slot)->refCount <= 0)
+ CStreaming::RemoveModel(slot + STREAM_OFFSET_TXD);
+}
+
+void
+CTxdStore::RemoveRefWithoutDelete(int slot)
+{
+ GetSlot(slot)->refCount--;
+}
+
+bool
+CTxdStore::LoadTxd(int slot, RwStream *stream)
+{
+ TxdDef *def = GetSlot(slot);
+
+ if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){
+ def->texDict = RwTexDictionaryGtaStreamRead(stream);
+ return def->texDict != nil;
+ }
+ printf("Failed to load TXD\n");
+ return false;
+}
+
+bool
+CTxdStore::LoadTxd(int slot, const char *filename)
+{
+ RwStream *stream;
+ bool ret;
+
+ ret = false;
+ _rwD3D8TexDictionaryEnableRasterFormatConversion(true);
+ do
+ stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
+ while(stream == nil);
+ ret = LoadTxd(slot, stream);
+ RwStreamClose(stream, nil);
+ return ret;
+}
+
+bool
+CTxdStore::StartLoadTxd(int slot, RwStream *stream)
+{
+ TxdDef *def = GetSlot(slot);
+ if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){
+ def->texDict = RwTexDictionaryGtaStreamRead1(stream);
+ return def->texDict != nil;
+ }else{
+ printf("Failed to load TXD\n");
+ return false;
+ }
+}
+
+bool
+CTxdStore::FinishLoadTxd(int slot, RwStream *stream)
+{
+ TxdDef *def = GetSlot(slot);
+ def->texDict = RwTexDictionaryGtaStreamRead2(stream, def->texDict);
+ return def->texDict != nil;
+}
+
+void
+CTxdStore::RemoveTxd(int slot)
+{
+ TxdDef *def = GetSlot(slot);
+ if(def->texDict)
+ RwTexDictionaryDestroy(def->texDict);
+ def->texDict = nil;
+}
+
+STARTPATCHES
+ InjectHook(0x527440, CTxdStore::Initialize, PATCH_JUMP);
+ InjectHook(0x527470, CTxdStore::Shutdown, PATCH_JUMP);
+ InjectHook(0x527490, CTxdStore::GameShutdown, PATCH_JUMP);
+ InjectHook(0x5274E0, CTxdStore::AddTxdSlot, PATCH_JUMP);
+ InjectHook(0x5275D0, CTxdStore::FindTxdSlot, PATCH_JUMP);
+ InjectHook(0x527590, CTxdStore::GetTxdName, PATCH_JUMP);
+ InjectHook(0x527900, CTxdStore::PushCurrentTxd, PATCH_JUMP);
+ InjectHook(0x527910, CTxdStore::PopCurrentTxd, PATCH_JUMP);
+ InjectHook(0x5278C0, CTxdStore::SetCurrentTxd, PATCH_JUMP);
+ InjectHook(0x527830, CTxdStore::Create, PATCH_JUMP);
+ InjectHook(0x527A00, CTxdStore::GetNumRefs, PATCH_JUMP);
+ InjectHook(0x527930, CTxdStore::AddRef, PATCH_JUMP);
+ InjectHook(0x527970, CTxdStore::RemoveRef, PATCH_JUMP);
+ InjectHook(0x5279C0, CTxdStore::RemoveRefWithoutDelete, PATCH_JUMP);
+ InjectHook(0x527700, (bool (*)(int, RwStream*))CTxdStore::LoadTxd, PATCH_JUMP);
+ InjectHook(0x5276B0, (bool (*)(int, const char*))CTxdStore::LoadTxd, PATCH_JUMP);
+ InjectHook(0x527770, CTxdStore::StartLoadTxd, PATCH_JUMP);
+ InjectHook(0x5277E0, CTxdStore::FinishLoadTxd, PATCH_JUMP);
+ InjectHook(0x527870, CTxdStore::RemoveTxd, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/TxdStore.h b/src/core/TxdStore.h
new file mode 100644
index 00000000..a9e57d31
--- /dev/null
+++ b/src/core/TxdStore.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "templates.h"
+
+struct TxdDef {
+ RwTexDictionary *texDict;
+ int refCount;
+ char name[20];
+};
+
+class CTxdStore
+{
+ static CPool<TxdDef,TxdDef> *&ms_pTxdPool;
+ static RwTexDictionary *&ms_pStoredTxd;
+public:
+ static void Initialize(void);
+ static void Shutdown(void);
+ static void GameShutdown(void);
+ static int AddTxdSlot(const char *name);
+ static void RemoveTxdSlot(int slot);
+ static int FindTxdSlot(const char *name);
+ static char *GetTxdName(int slot);
+ static void PushCurrentTxd(void);
+ static void PopCurrentTxd(void);
+ static void SetCurrentTxd(int slot);
+ static void Create(int slot);
+ static int GetNumRefs(int slot);
+ static void AddRef(int slot);
+ static void RemoveRef(int slot);
+ static void RemoveRefWithoutDelete(int slot);
+ static bool LoadTxd(int slot, RwStream *stream);
+ static bool LoadTxd(int slot, const char *filename);
+ static bool StartLoadTxd(int slot, RwStream *stream);
+ static bool FinishLoadTxd(int slot, RwStream *stream);
+ static void RemoveTxd(int slot);
+
+ static TxdDef *GetSlot(int slot) {
+ assert(slot >= 0);
+ assert(ms_pTxdPool);
+ assert(slot < ms_pTxdPool->GetSize());
+ return ms_pTxdPool->GetSlot(slot);
+ }
+ static bool isTxdLoaded(int slot);
+};
diff --git a/src/core/User.cpp b/src/core/User.cpp
new file mode 100644
index 00000000..c9cb97cc
--- /dev/null
+++ b/src/core/User.cpp
@@ -0,0 +1,176 @@
+#include "common.h"
+#include "patcher.h"
+
+#include "DMAudio.h"
+#include "Hud.h"
+#include "Replay.h"
+#include "Timer.h"
+#include "Script.h"
+#include "User.h"
+
+CPlaceName& CUserDisplay::PlaceName = *(CPlaceName*)0x8F29BC;
+COnscreenTimer& CUserDisplay::OnscnTimer = *(COnscreenTimer*)0x862238;
+CPager& CUserDisplay::Pager = *(CPager*)0x8F2744;
+CCurrentVehicle& CUserDisplay::CurrentVehicle = *(CCurrentVehicle*)0x8F5FE8;
+
+void COnscreenTimer::Init() {
+ m_bDisabled = false;
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ m_sEntries[i].m_nTimerOffset = 0;
+ m_sEntries[i].m_nCounterOffset = 0;
+
+ for(uint32 j = 0; j < 10; j++) {
+ m_sEntries[i].m_aTimerText[j] = 0;
+ m_sEntries[i].m_aCounterText[j] = 0;
+ }
+
+ m_sEntries[i].m_nType = 0;
+ m_sEntries[i].m_bTimerProcessed = 0;
+ m_sEntries[i].m_bCounterProcessed = 0;
+ }
+}
+
+void COnscreenTimer::Process() {
+ if(!CReplay::IsPlayingBack() && !m_bDisabled) {
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ m_sEntries[i].Process();
+ }
+ }
+}
+
+void COnscreenTimer::ProcessForDisplay() {
+ if(CHud::m_Wants_To_Draw_Hud) {
+ m_bProcessed = false;
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ if(m_sEntries[i].ProcessForDisplay()) {
+ m_bProcessed = true;
+ }
+ }
+ }
+}
+
+void COnscreenTimer::ClearCounter(uint32 offset) {
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ if(offset == m_sEntries[i].m_nCounterOffset) {
+ m_sEntries[i].m_nCounterOffset = 0;
+ m_sEntries[i].m_aCounterText[0] = 0;
+ m_sEntries[i].m_nType = 0;
+ m_sEntries[i].m_bCounterProcessed = 0;
+ }
+ }
+}
+
+void COnscreenTimer::ClearClock(uint32 offset) {
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ if(offset == m_sEntries[i].m_nTimerOffset) {
+ m_sEntries[i].m_nTimerOffset = 0;
+ m_sEntries[i].m_aTimerText[0] = 0;
+ m_sEntries[i].m_bTimerProcessed = 0;
+ }
+ }
+}
+
+void COnscreenTimer::AddCounter(uint32 offset, uint16 type, char* text) {
+ uint32 i = 0;
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ if(m_sEntries[i].m_nCounterOffset == 0) {
+ break;
+ }
+ return;
+ }
+
+ m_sEntries[i].m_nCounterOffset = offset;
+ if(text) {
+ strncpy(m_sEntries[i].m_aCounterText, text, 10);
+ } else {
+ m_sEntries[i].m_aCounterText[0] = 0;
+ }
+
+ m_sEntries[i].m_nType = type;
+}
+
+void COnscreenTimer::AddClock(uint32 offset, char* text) {
+ uint32 i = 0;
+ for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) {
+ if(m_sEntries[i].m_nTimerOffset == 0) {
+ break;
+ }
+ return;
+ }
+
+ m_sEntries[i].m_nTimerOffset = offset;
+ if(text) {
+ strncpy(m_sEntries[i].m_aTimerText, text, 10);
+ } else {
+ m_sEntries[i].m_aTimerText[0] = 0;
+ }
+}
+
+void COnscreenTimerEntry::Process() {
+ if(m_nTimerOffset == 0) {
+ return;
+ }
+
+ uint32* timerPtr = (uint32*)&CTheScripts::ScriptSpace[m_nTimerOffset];
+ uint32 oldTime = *timerPtr;
+ int32 newTime = int32(oldTime - uint32(20.0f * CTimer::GetTimeStep()));
+ if(newTime < 0) {
+ *timerPtr = 0;
+ m_bTimerProcessed = 0;
+ m_nTimerOffset = 0;
+ m_aTimerText[0] = 0;
+ } else {
+ *timerPtr = (uint32)newTime;
+ uint32 oldTimeSeconds = oldTime / 1000;
+ if(oldTimeSeconds <= 11 && newTime / 1000 != oldTimeSeconds) {
+ // TODO: use an enum here
+ DMAudio.PlayFrontEndSound(0x93, newTime / 1000);
+ }
+ }
+}
+
+bool COnscreenTimerEntry::ProcessForDisplay() {
+ m_bTimerProcessed = false;
+ m_bCounterProcessed = false;
+
+ if(m_nTimerOffset == 0 && m_nCounterOffset == 0) {
+ return false;
+ }
+
+ if(m_nTimerOffset != 0) {
+ m_bTimerProcessed = true;
+ ProcessForDisplayTimer();
+ }
+
+ if(m_nCounterOffset != 0) {
+ m_bCounterProcessed = true;
+ ProcessForDisplayCounter();
+ }
+ return true;
+}
+
+int COnscreenTimerEntry::ProcessForDisplayTimer() {
+ uint32 time = *(uint32*)&CTheScripts::ScriptSpace[m_nTimerOffset];
+ return sprintf(m_bTimerBuffer, "%02d:%02d", time / 1000 / 60,
+ time / 1000 % 60);
+}
+
+int COnscreenTimerEntry::ProcessForDisplayCounter() {
+ uint32 counter = *(uint32*)&CTheScripts::ScriptSpace[m_nCounterOffset];
+ return sprintf(m_bCounterBuffer, "%d", counter);
+}
+
+STARTPATCHES
+ InjectHook(0x429160, &COnscreenTimerEntry::Process, PATCH_JUMP);
+ InjectHook(0x429110, &COnscreenTimerEntry::ProcessForDisplay, PATCH_JUMP);
+ InjectHook(0x429080, &COnscreenTimerEntry::ProcessForDisplayTimer, PATCH_JUMP);
+ InjectHook(0x4290F0, &COnscreenTimerEntry::ProcessForDisplayCounter, PATCH_JUMP);
+
+ InjectHook(0x429220, &COnscreenTimer::Init, PATCH_JUMP);
+ InjectHook(0x429320, &COnscreenTimer::Process, PATCH_JUMP);
+ InjectHook(0x4292E0, &COnscreenTimer::ProcessForDisplay, PATCH_JUMP);
+ InjectHook(0x429450, &COnscreenTimer::ClearCounter, PATCH_JUMP);
+ InjectHook(0x429410, &COnscreenTimer::ClearClock, PATCH_JUMP);
+ InjectHook(0x4293B0, &COnscreenTimer::AddCounter, PATCH_JUMP);
+ InjectHook(0x429350, &COnscreenTimer::AddClock, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/User.h b/src/core/User.h
new file mode 100644
index 00000000..8b744c7e
--- /dev/null
+++ b/src/core/User.h
@@ -0,0 +1,64 @@
+#pragma once
+
+class COnscreenTimerEntry
+{
+public:
+ uint32 m_nTimerOffset;
+ uint32 m_nCounterOffset;
+ char m_aTimerText[10];
+ char m_aCounterText[10];
+ uint16 m_nType;
+ char m_bCounterBuffer[42];
+ char m_bTimerBuffer[42];
+ bool m_bTimerProcessed;
+ bool m_bCounterProcessed;
+
+ void Process();
+ bool ProcessForDisplay();
+
+ int ProcessForDisplayTimer();
+ int ProcessForDisplayCounter();
+};
+
+static_assert(sizeof(COnscreenTimerEntry) == 0x74, "COnscreenTimerEntry: error");
+
+class COnscreenTimer
+{
+public:
+ COnscreenTimerEntry m_sEntries[NUMONSCREENTIMERENTRIES];
+ bool m_bProcessed;
+ bool m_bDisabled;
+
+ void Init();
+ void Process();
+ void ProcessForDisplay();
+
+ void ClearCounter(uint32 offset);
+ void ClearClock(uint32 offset);
+
+ void AddCounter(uint32 offset, uint16 type, char* text);
+ void AddClock(uint32 offset, char* text);
+};
+
+static_assert(sizeof(COnscreenTimer) == 0x78, "COnscreenTimer: error");
+
+class CPlaceName
+{
+};
+
+class CCurrentVehicle
+{
+};
+
+class CPager
+{
+};
+
+class CUserDisplay
+{
+public:
+ static CPlaceName &PlaceName;
+ static COnscreenTimer &OnscnTimer;
+ static CPager &Pager;
+ static CCurrentVehicle &CurrentVehicle;
+};
diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp
new file mode 100644
index 00000000..21853308
--- /dev/null
+++ b/src/core/Wanted.cpp
@@ -0,0 +1,129 @@
+#include "common.h"
+#include "patcher.h"
+#include "Wanted.h"
+
+int32 &CWanted::MaximumWantedLevel = *(int32*)0x5F7714;
+
+bool CWanted::AreSwatRequired()
+{
+ return m_nWantedLevel >= 4;
+}
+
+bool CWanted::AreFbiRequired()
+{
+ return m_nWantedLevel >= 5;
+}
+
+bool CWanted::AreArmyRequired()
+{
+ return m_nWantedLevel >= 6;
+}
+
+int CWanted::NumOfHelisRequired()
+{
+ if (m_IsIgnoredByCops)
+ return 0;
+
+ // Return value is number of helicopters, no need to name them.
+ switch (m_nWantedLevel) {
+ case WANTEDLEVEL_3:
+ case WANTEDLEVEL_4:
+ return 1;
+ case WANTEDLEVEL_5:
+ case WANTEDLEVEL_6:
+ return 2;
+ default:
+ return 0;
+ };
+}
+
+void CWanted::SetWantedLevel(int32 level)
+{
+ ClearQdCrimes();
+ switch (level) {
+ case NOTWANTED:
+ m_nChaos = 0;
+ break;
+ case WANTEDLEVEL_1:
+ m_nChaos = 60;
+ break;
+ case WANTEDLEVEL_2:
+ m_nChaos = 220;
+ break;
+ case WANTEDLEVEL_3:
+ m_nChaos = 420;
+ break;
+ case WANTEDLEVEL_4:
+ m_nChaos = 820;
+ break;
+ case WANTEDLEVEL_5:
+ m_nChaos = 1620;
+ break;
+ case WANTEDLEVEL_6:
+ m_nChaos = 3220;
+ break;
+ default:
+ if (level > MaximumWantedLevel)
+ m_nChaos = MaximumWantedLevel;
+ break;
+ }
+ UpdateWantedLevel();
+}
+
+void CWanted::ClearQdCrimes()
+{
+ for (int i = 0; i < 16; i++) {
+ m_sCrimes[i].m_eCrimeType = CRIME_NONE;
+ };
+}
+
+void CWanted::UpdateWantedLevel()
+{
+ int32 CurrWantedLevel = m_nWantedLevel;
+
+ if (m_nChaos >= 0 && m_nChaos < 40) {
+ m_nWantedLevel = NOTWANTED;
+ m_MaximumLawEnforcerVehicles = 0;
+ m_MaxCops = 0;
+ m_RoadblockDensity = 0;
+ }
+ else if (m_nChaos >= 40 && m_nChaos < 200) {
+ m_nWantedLevel = WANTEDLEVEL_1;
+ m_MaximumLawEnforcerVehicles = 1;
+ m_MaxCops = 1;
+ m_RoadblockDensity = 0;
+ }
+ else if (m_nChaos >= 200 && m_nChaos < 400) {
+ m_nWantedLevel = WANTEDLEVEL_2;
+ m_MaximumLawEnforcerVehicles = 2;
+ m_MaxCops = 3;
+ m_RoadblockDensity = 0;
+ }
+ else if (m_nChaos >= 400 && m_nChaos < 800) {
+ m_nWantedLevel = WANTEDLEVEL_3;
+ m_MaximumLawEnforcerVehicles = 2;
+ m_MaxCops = 4;
+ m_RoadblockDensity = 4;
+ }
+ else if (m_nChaos >= 800 && m_nChaos < 1600) {
+ m_nWantedLevel = WANTEDLEVEL_4;
+ m_MaximumLawEnforcerVehicles = 2;
+ m_MaxCops = 6;
+ m_RoadblockDensity = 8;
+ }
+ else if (m_nChaos >= 1600 && m_nChaos < 3200) {
+ m_nWantedLevel = WANTEDLEVEL_5;
+ m_MaximumLawEnforcerVehicles = 3;
+ m_MaxCops = 8;
+ m_RoadblockDensity = 10;
+ }
+ else if (m_nChaos >= 3200) {
+ m_nWantedLevel = WANTEDLEVEL_6;
+ m_MaximumLawEnforcerVehicles = 3;
+ m_MaxCops = 10;
+ m_RoadblockDensity = 12;
+ }
+
+ if (CurrWantedLevel != m_nWantedLevel)
+ m_nLastWantedLevelChange = CTimer::GetTimeInMilliseconds();
+} \ No newline at end of file
diff --git a/src/core/Wanted.h b/src/core/Wanted.h
new file mode 100644
index 00000000..d14bb905
--- /dev/null
+++ b/src/core/Wanted.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "Entity.h"
+#include "math/Vector.h"
+#include "CopPed.h"
+
+enum eWantedLevel {
+ NOTWANTED,
+ WANTEDLEVEL_1,
+ WANTEDLEVEL_2,
+ WANTEDLEVEL_3,
+ WANTEDLEVEL_4,
+ WANTEDLEVEL_5,
+ WANTEDLEVEL_6,
+};
+
+class CWanted
+{
+public:
+ int32 m_nChaos;
+ int32 m_nLastUpdateTime;
+ int32 m_nLastWantedLevelChange;
+ float m_fCrimeSensitivity;
+ uint8 m_CurrentCops;
+ uint8 m_MaxCops;
+ uint8 m_MaximumLawEnforcerVehicles;
+ int8 field_19;
+ int16 m_RoadblockDensity;
+ uint8 m_IsIgnoredByCops : 1;
+ uint8 m_IsIgnoredByEveryOne : 1;
+ uint8 m_IsSwatRequired : 1;
+ uint8 m_IsFbiRequired : 1;
+ uint8 m_IdArmyRequired : 1;
+ int8 field_23;
+ int32 m_nWantedLevel;
+ CCrime m_sCrimes[16];
+ CCopPed *m_pCops[10];
+ static int32 &MaximumWantedLevel;
+
+public:
+ bool AreSwatRequired();
+ bool AreFbiRequired();
+ bool AreArmyRequired();
+ int NumOfHelisRequired();
+ void SetWantedLevel(int32);
+ void ClearQdCrimes();
+ void UpdateWantedLevel();
+};
+
+static_assert(sizeof(CWanted) == 0x204, "CWanted: error");
diff --git a/src/core/World.cpp b/src/core/World.cpp
new file mode 100644
index 00000000..538e15c5
--- /dev/null
+++ b/src/core/World.cpp
@@ -0,0 +1,718 @@
+#include "common.h"
+#include "patcher.h"
+#include "Entity.h"
+#include "Ped.h"
+#include "PlayerPed.h"
+#include "Vehicle.h"
+#include "Object.h"
+#include "Camera.h"
+#include "DMAudio.h"
+#include "CarCtrl.h"
+#include "Garages.h"
+#include "TempColModels.h"
+#include "World.h"
+
+CPtrList *CWorld::ms_bigBuildingsList = (CPtrList*)0x6FAB60;
+CPtrList &CWorld::ms_listMovingEntityPtrs = *(CPtrList*)0x8F433C;
+CSector (*CWorld::ms_aSectors)[NUMSECTORS_X] = (CSector (*)[NUMSECTORS_Y])0x665608;
+uint16 &CWorld::ms_nCurrentScanCode = *(uint16*)0x95CC64;
+
+uint8 &CWorld::PlayerInFocus = *(uint8 *)0x95CD61;
+CPlayerInfo *CWorld::Players = (CPlayerInfo *)0x9412F0;
+bool &CWorld::bNoMoreCollisionTorque = *(bool*)0x95CDCC;
+CEntity *&CWorld::pIgnoreEntity = *(CEntity**)0x8F6494;
+bool &CWorld::bIncludeDeadPeds = *(bool*)0x95CD8F;
+bool &CWorld::bSecondShift = *(bool*)0x95CD54;
+bool &CWorld::bForceProcessControl = *(bool*)0x95CD6C;
+bool &CWorld::bProcessCutsceneOnly = *(bool*)0x95CD8B;
+
+void
+CWorld::Add(CEntity *ent)
+{
+ if(ent->IsVehicle() || ent->IsPed())
+ DMAudio.SetEntityStatus(((CPhysical*)ent)->m_audioEntityId, true);
+
+ if(ent->bIsBIGBuilding)
+ ms_bigBuildingsList[ent->m_level].InsertItem(ent);
+ else
+ ent->Add();
+
+ if(ent->IsBuilding() || ent->IsDummy())
+ return;
+
+ if(!ent->bIsStatic)
+ ((CPhysical*)ent)->AddToMovingList();
+}
+
+void
+CWorld::Remove(CEntity *ent)
+{
+ if(ent->IsVehicle() || ent->IsPed())
+ DMAudio.SetEntityStatus(((CPhysical*)ent)->m_audioEntityId, false);
+
+ if(ent->bIsBIGBuilding)
+ ms_bigBuildingsList[ent->m_level].RemoveItem(ent);
+ else
+ ent->Remove();
+
+ if(ent->IsBuilding() || ent->IsDummy())
+ return;
+
+ if(!ent->bIsStatic)
+ ((CPhysical*)ent)->RemoveFromMovingList();
+}
+
+void
+CWorld::ClearScanCodes(void)
+{
+ CPtrNode *node;
+ for(int i = 0; i < NUMSECTORS_Y; i++)
+ for(int j = 0; j < NUMSECTORS_X; j++){
+ CSector *s = &ms_aSectors[i][j];
+ for(node = s->m_lists[ENTITYLIST_BUILDINGS].first; node; node = node->next)
+ ((CEntity*)node->item)->m_scanCode = 0;
+ for(node = s->m_lists[ENTITYLIST_VEHICLES].first; node; node = node->next)
+ ((CEntity*)node->item)->m_scanCode = 0;
+ for(node = s->m_lists[ENTITYLIST_PEDS].first; node; node = node->next)
+ ((CEntity*)node->item)->m_scanCode = 0;
+ for(node = s->m_lists[ENTITYLIST_OBJECTS].first; node; node = node->next)
+ ((CEntity*)node->item)->m_scanCode = 0;
+ for(node = s->m_lists[ENTITYLIST_DUMMIES].first; node; node = node->next)
+ ((CEntity*)node->item)->m_scanCode = 0;
+ }
+}
+
+bool
+CWorld::CameraToIgnoreThisObject(CEntity *ent)
+{
+ if(CGarages::IsModelIndexADoor(ent->GetModelIndex()))
+ return false;
+ return ((CObject*)ent)->m_bCameraToAvoidThisObject != 1;
+}
+
+bool
+CWorld::ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ int x, xstart, xend;
+ int y, ystart, yend;
+ int y1, y2;
+ float dist;
+
+ AdvanceCurrentScanCode();
+
+ entity = nil;
+ dist = 1.0f;
+
+ xstart = GetSectorIndexX(point1.x);
+ ystart = GetSectorIndexX(point1.y);
+ xend = GetSectorIndexX(point2.x);
+ yend = GetSectorIndexX(point2.y);
+
+#define LOSARGS CColLine(point1, point2), point, dist, entity, checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects
+
+ if(xstart == xend && ystart == yend){
+ // Only one sector
+ return ProcessLineOfSightSector(*GetSector(xstart, ystart), LOSARGS);
+ }else if(xstart == xend){
+ // Only step in y
+ if(ystart < yend)
+ for(y = ystart; y <= yend; y++)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+ else
+ for(y = ystart; y >= yend; y--)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+ return dist < 1.0f;
+ }else if(ystart == yend){
+ // Only step in x
+ if(xstart < xend)
+ for(x = xstart; x <= xend; x++)
+ ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS);
+ else
+ for(x = xstart; x >= xend; x--)
+ ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS);
+ return dist < 1.0f;
+ }else{
+ if(point1.x < point2.x){
+ // Step from left to right
+ float m = (point2.y - point1.y) / (point2.x - point1.x);
+
+ y1 = ystart;
+ y2 = GetSectorIndexY((GetWorldX(xstart+1) - point1.x)*m + point1.y);
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+
+ for(x = xstart+1; x < xend; x++){
+ y1 = y2;
+ y2 = GetSectorIndexY((GetWorldX(x+1) - point1.x)*m + point1.y);
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(x, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(x, y), LOSARGS);
+ }
+
+ y1 = y2;
+ y2 = yend;
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS);
+ }else{
+ // Step from right to left
+ float m = (point2.y - point1.y) / (point2.x - point1.x);
+
+ y1 = ystart;
+ y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x)*m + point1.y);
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS);
+
+ for(x = xstart-1; x > xend; x--){
+ y1 = y2;
+ y2 = GetSectorIndexY((GetWorldX(x) - point1.x)*m + point1.y);
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(x, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(x, y), LOSARGS);
+ }
+
+ y1 = y2;
+ y2 = yend;
+ if(y1 < y2)
+ for(y = y1; y <= y2; y++)
+ ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS);
+ else
+ for(y = y1; y >= y2; y--)
+ ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS);
+ }
+ return dist < 1.0f;
+ }
+
+#undef LOSARGS
+}
+
+bool
+CWorld::ProcessLineOfSightSector(CSector &sector, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ float mindist = dist;
+ bool deadPeds = !!bIncludeDeadPeds;
+ bIncludeDeadPeds = false;
+
+ if(checkBuildings){
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, ignoreSeeThrough);
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough);
+ }
+
+ if(checkVehicles){
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, ignoreSeeThrough);
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough);
+ }
+
+ if(checkPeds){
+ if(deadPeds)
+ bIncludeDeadPeds = true;
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, ignoreSeeThrough);
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough);
+ bIncludeDeadPeds = false;
+ }
+
+ if(checkObjects){
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, ignoreSeeThrough, ignoreSomeObjects);
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, ignoreSomeObjects);
+ }
+
+ if(checkDummies){
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, ignoreSeeThrough);
+ ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough);
+ }
+
+ bIncludeDeadPeds = deadPeds;
+
+ if(mindist < dist){
+ dist = mindist;
+ return true;
+ }else
+ return false;
+}
+
+bool
+CWorld::ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ bool deadPeds = false;
+ float mindist = dist;
+ CPtrNode *node;
+ CEntity *e;
+ CColModel *colmodel;
+
+ if(list.first && bIncludeDeadPeds && ((CEntity*)list.first->item)->IsPed())
+ deadPeds = true;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(e->m_scanCode != GetCurrentScanCode() &&
+ e != pIgnoreEntity &&
+ (e->bUsesCollision || deadPeds) &&
+ !(ignoreSomeObjects && CameraToIgnoreThisObject(e))){
+ colmodel = nil;
+ e->m_scanCode = GetCurrentScanCode();
+
+ if(e->IsPed()){
+ if(e->bUsesCollision ||
+ deadPeds && ((CPed*)e)->m_nPedState == PED_DEAD){
+ if(((CPed*)e)->UseGroundColModel())
+ colmodel = &CTempColModels::ms_colModelPedGroundHit;
+ else
+ colmodel = ((CPedModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()))->GetHitColModel();
+ }else
+ colmodel = nil;
+ }else if(e->bUsesCollision)
+ colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel();
+
+ if(colmodel &&
+ CCollision::ProcessLineOfSight(line, e->GetMatrix(), *colmodel, point, dist, ignoreSeeThrough))
+ entity = e;
+ }
+ }
+
+ if(mindist < dist){
+ dist = mindist;
+ return true;
+ }else
+ return false;
+}
+
+bool
+CWorld::ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly)
+{
+ AdvanceCurrentScanCode();
+ CVector point2(point1.x, point1.y, z2);
+ return CWorld::ProcessVerticalLineSector(*GetSector(GetSectorIndexX(point1.x), GetSectorIndexX(point1.y)),
+ CColLine(point1, point2), point, entity,
+ checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, poly);
+}
+
+bool
+CWorld::ProcessVerticalLineSector(CSector &sector, const CColLine &line, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly)
+{
+ float mindist = 1.0f;
+
+ if(checkBuildings){
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, ignoreSeeThrough, poly);
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly);
+ }
+
+ if(checkVehicles){
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, ignoreSeeThrough, poly);
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly);
+ }
+
+ if(checkPeds){
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, ignoreSeeThrough, poly);
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly);
+ }
+
+ if(checkObjects){
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, ignoreSeeThrough, poly);
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly);
+ }
+
+ if(checkDummies){
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, ignoreSeeThrough, poly);
+ ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly);
+ }
+
+ return mindist < 1.0f;
+}
+
+bool
+CWorld::ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly)
+{
+ float mindist = dist;
+ CPtrNode *node;
+ CEntity *e;
+ CColModel *colmodel;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(e->m_scanCode != GetCurrentScanCode() &&
+ e->bUsesCollision){
+ e->m_scanCode = GetCurrentScanCode();
+
+ colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel();
+ if(CCollision::ProcessVerticalLine(line, e->GetMatrix(), *colmodel, point, dist, ignoreSeeThrough, poly))
+ entity = e;
+ }
+ }
+
+ if(mindist < dist){
+ dist = mindist;
+ return true;
+ }else
+ return false;
+}
+
+bool
+CWorld::GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ int x, xstart, xend;
+ int y, ystart, yend;
+ int y1, y2;
+
+ AdvanceCurrentScanCode();
+
+ xstart = GetSectorIndexX(point1.x);
+ ystart = GetSectorIndexX(point1.y);
+ xend = GetSectorIndexX(point2.x);
+ yend = GetSectorIndexX(point2.y);
+
+#define LOSARGS CColLine(point1, point2), checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects
+
+ if(xstart == xend && ystart == yend){
+ // Only one sector
+ return GetIsLineOfSightSectorClear(*GetSector(xstart, ystart), LOSARGS);
+ }else if(xstart == xend){
+ // Only step in y
+ if(ystart < yend){
+ for(y = ystart; y <= yend; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }else{
+ for(y = ystart; y >= yend; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }
+ }else if(ystart == yend){
+ // Only step in x
+ if(xstart < xend){
+ for(x = xstart; x <= xend; x++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS))
+ return false;
+ }else{
+ for(x = xstart; x >= xend; x--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS))
+ return false;
+ }
+ }else{
+ if(point1.x < point2.x){
+ // Step from left to right
+ float m = (point2.y - point1.y) / (point2.x - point1.x);
+
+ y1 = ystart;
+ y2 = GetSectorIndexY((GetWorldX(xstart+1) - point1.x)*m + point1.y);
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }
+
+ for(x = xstart+1; x < xend; x++){
+ y1 = y2;
+ y2 = GetSectorIndexY((GetWorldX(x+1) - point1.x)*m + point1.y);
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS))
+ return false;
+ }
+ }
+
+ y1 = y2;
+ y2 = yend;
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS))
+ return false;
+ }
+ }else{
+ // Step from right to left
+ float m = (point2.y - point1.y) / (point2.x - point1.x);
+
+ y1 = ystart;
+ y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x)*m + point1.y);
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS))
+ return false;
+ }
+
+ for(x = xstart-1; x > xend; x--){
+ y1 = y2;
+ y2 = GetSectorIndexY((GetWorldX(x) - point1.x)*m + point1.y);
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS))
+ return false;
+ }
+ }
+
+ y1 = y2;
+ y2 = yend;
+ if(y1 < y2){
+ for(y = y1; y <= y2; y++)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS))
+ return false;
+ }else{
+ for(y = y1; y >= y2; y--)
+ if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS))
+ return false;
+ }
+ }
+ }
+
+ return true;
+
+#undef LOSARGS
+}
+
+bool
+CWorld::GetIsLineOfSightSectorClear(CSector &sector, const CColLine &line, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ if(checkBuildings){
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS], line, ignoreSeeThrough))
+ return false;
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, ignoreSeeThrough))
+ return false;
+ }
+
+ if(checkVehicles){
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES], line, ignoreSeeThrough))
+ return false;
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, ignoreSeeThrough))
+ return false;
+ }
+
+ if(checkPeds){
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS], line, ignoreSeeThrough))
+ return false;
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, ignoreSeeThrough))
+ return false;
+ }
+
+ if(checkObjects){
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS], line, ignoreSeeThrough, ignoreSomeObjects))
+ return false;
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, ignoreSeeThrough, ignoreSomeObjects))
+ return false;
+ }
+
+ if(checkDummies){
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES], line, ignoreSeeThrough))
+ return false;
+ if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, ignoreSeeThrough))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+CWorld::GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, bool ignoreSomeObjects)
+{
+ CPtrNode *node;
+ CEntity *e;
+ CColModel *colmodel;
+
+ for(node = list.first; node; node = node->next){
+ e = (CEntity*)node->item;
+ if(e->m_scanCode != GetCurrentScanCode() &&
+ e->bUsesCollision){
+
+ e->m_scanCode = GetCurrentScanCode();
+
+ if(e != pIgnoreEntity &&
+ !(ignoreSomeObjects && CameraToIgnoreThisObject(e))){
+
+ colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel();
+
+ if(CCollision::TestLineOfSight(line, e->GetMatrix(), *colmodel, ignoreSeeThrough))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+float
+CWorld::FindGroundZForCoord(float x, float y)
+{
+ CColPoint point;
+ CEntity *ent;
+ if(ProcessVerticalLine(CVector(x, y, 1000.0f), -1000.0f, point, ent, true, false, false, false, true, false, nil))
+ return point.point.z;
+ else
+ return 20.0f;
+}
+
+float
+CWorld::FindGroundZFor3DCoord(float x, float y, float z, bool *found)
+{
+ CColPoint point;
+ CEntity *ent;
+ if(ProcessVerticalLine(CVector(x, y, z), -1000.0f, point, ent, true, false, false, false, false, false, nil)){
+ if(found)
+ *found = true;
+ return point.point.z;
+ }else{
+ if(found)
+ *found = false;
+ return 0.0f;
+ }
+}
+
+float
+CWorld::FindRoofZFor3DCoord(float x, float y, float z, bool *found)
+{
+ CColPoint point;
+ CEntity *ent;
+ if(ProcessVerticalLine(CVector(x, y, z), 1000.0f, point, ent, true, false, false, false, true, false, nil)){
+ if(found)
+ *found = true;
+ return point.point.z;
+ }else{
+ if(found == nil)
+ printf("THERE IS NO MAP BELOW THE FOLLOWING COORS:%f %f %f. (FindGroundZFor3DCoord)\n", x, y, z);
+ if(found)
+ *found = false;
+ return 20.0f;
+ }
+}
+
+CPlayerPed*
+FindPlayerPed(void)
+{
+ return CWorld::Players[CWorld::PlayerInFocus].m_pPed;
+}
+
+CVehicle*
+FindPlayerVehicle(void)
+{
+ CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed;
+ if(ped->bInVehicle && ped->m_pMyVehicle)
+ return ped->m_pMyVehicle;
+ else
+ return nil;
+}
+
+CVehicle*
+FindPlayerTrain(void)
+{
+ if(FindPlayerVehicle() && FindPlayerVehicle()->IsTrain())
+ return FindPlayerVehicle();
+ else
+ return nil;
+}
+
+CEntity*
+FindPlayerEntity(void)
+{
+ CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed;
+ if(ped->bInVehicle && ped->m_pMyVehicle)
+ return ped->m_pMyVehicle;
+ else
+ return ped;
+}
+
+CVector
+FindPlayerCoors(void)
+{
+ CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed;
+ if(ped->bInVehicle && ped->m_pMyVehicle)
+ return ped->m_pMyVehicle->GetPosition();
+ else
+ return ped->GetPosition();
+}
+
+CVector&
+FindPlayerSpeed(void)
+{
+ CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed;
+ if(ped->bInVehicle && ped->m_pMyVehicle)
+ return ped->m_pMyVehicle->m_vecMoveSpeed;
+ else
+ return ped->m_vecMoveSpeed;
+}
+
+CVector&
+FindPlayerCentreOfWorld(int32 player)
+{
+ if(CCarCtrl::bCarsGeneratedAroundCamera)
+ return TheCamera.GetPosition();
+ if(CWorld::Players[player].m_pRemoteVehicle)
+ return CWorld::Players[player].m_pRemoteVehicle->GetPosition();
+ if(FindPlayerVehicle())
+ return FindPlayerVehicle()->GetPosition();
+ return CWorld::Players[player].m_pPed->GetPosition();
+}
+
+CVector&
+FindPlayerCentreOfWorld_NoSniperShift(void)
+{
+ if(CCarCtrl::bCarsGeneratedAroundCamera)
+ return TheCamera.GetPosition();
+ if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle)
+ return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetPosition();
+ if(FindPlayerVehicle())
+ return FindPlayerVehicle()->GetPosition();
+ return CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetPosition();
+}
+
+float
+FindPlayerHeading(void)
+{
+ if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle)
+ return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetForward().Heading();
+ if(FindPlayerVehicle())
+ return FindPlayerVehicle()->GetForward().Heading();
+ return CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetForward().Heading();
+}
+
+STARTPATCHES
+ InjectHook(0x4AE930, CWorld::Add, PATCH_JUMP);
+ InjectHook(0x4AE9D0, CWorld::Remove, PATCH_JUMP);
+ InjectHook(0x4B1F60, CWorld::ClearScanCodes, PATCH_JUMP);
+ InjectHook(0x4AF970, CWorld::ProcessLineOfSight, PATCH_JUMP);
+ InjectHook(0x4B0A80, CWorld::ProcessLineOfSightSector, PATCH_JUMP);
+ InjectHook(0x4B0C70, CWorld::ProcessLineOfSightSectorList, PATCH_JUMP);
+ InjectHook(0x4B0DE0, CWorld::ProcessVerticalLine, PATCH_JUMP);
+ InjectHook(0x4B0EF0, CWorld::ProcessVerticalLineSector, PATCH_JUMP);
+ InjectHook(0x4B1090, CWorld::ProcessVerticalLineSectorList, PATCH_JUMP);
+ InjectHook(0x4AEAA0, CWorld::GetIsLineOfSightClear, PATCH_JUMP);
+ InjectHook(0x4B2000, CWorld::GetIsLineOfSightSectorClear, PATCH_JUMP);
+ InjectHook(0x4B2160, CWorld::GetIsLineOfSightSectorListClear, PATCH_JUMP);
+
+ InjectHook(0x4B3A80, CWorld::FindGroundZForCoord, PATCH_JUMP);
+ InjectHook(0x4B3AE0, CWorld::FindGroundZFor3DCoord, PATCH_JUMP);
+ InjectHook(0x4B3B50, CWorld::FindRoofZFor3DCoord, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/World.h b/src/core/World.h
new file mode 100644
index 00000000..3b7090da
--- /dev/null
+++ b/src/core/World.h
@@ -0,0 +1,119 @@
+#pragma once
+
+#include "Game.h"
+#include "Lists.h"
+#include "PlayerInfo.h"
+
+/* Sectors span from -2000 to 2000 in x and y.
+ * With 100x100 sectors, each is 40x40 units. */
+
+#define SECTOR_SIZE_X (40.0f)
+#define SECTOR_SIZE_Y (40.0f)
+
+#define NUMSECTORS_X (100)
+#define NUMSECTORS_Y (100)
+
+#define WORLD_SIZE_X (NUMSECTORS_X * SECTOR_SIZE_X)
+#define WORLD_SIZE_Y (NUMSECTORS_Y * SECTOR_SIZE_Y)
+
+#define WORLD_MIN_X (-2000.0f)
+#define WORLD_MIN_Y (-2000.0f)
+
+#define WORLD_MAX_X (WORLD_MIN_X + WORLD_SIZE_X)
+#define WORLD_MAX_Y (WORLD_MIN_Y + WORLD_SIZE_Y)
+
+enum
+{
+ ENTITYLIST_BUILDINGS,
+ ENTITYLIST_BUILDINGS_OVERLAP,
+ ENTITYLIST_OBJECTS,
+ ENTITYLIST_OBJECTS_OVERLAP,
+ ENTITYLIST_VEHICLES,
+ ENTITYLIST_VEHICLES_OVERLAP,
+ ENTITYLIST_PEDS,
+ ENTITYLIST_PEDS_OVERLAP,
+ ENTITYLIST_DUMMIES,
+ ENTITYLIST_DUMMIES_OVERLAP,
+
+ NUMSECTORENTITYLISTS
+};
+
+class CSector
+{
+public:
+ CPtrList m_lists[NUMSECTORENTITYLISTS];
+};
+static_assert(sizeof(CSector) == 0x28, "CSector: error");
+
+class CEntity;
+struct CColPoint;
+struct CColLine;
+struct CStoredCollPoly;
+
+class CWorld
+{
+ static CPtrList *ms_bigBuildingsList; // [4];
+ static CPtrList &ms_listMovingEntityPtrs;
+ static CSector (*ms_aSectors)[NUMSECTORS_X]; // [NUMSECTORS_Y][NUMSECTORS_X];
+ static uint16 &ms_nCurrentScanCode;
+
+public:
+ static uint8 &PlayerInFocus;
+ static CPlayerInfo *Players;
+ static CEntity *&pIgnoreEntity;
+ static bool &bIncludeDeadPeds;
+ static bool &bNoMoreCollisionTorque;
+ static bool &bSecondShift;
+ static bool &bForceProcessControl;
+ static bool &bProcessCutsceneOnly;
+
+ static void Remove(CEntity *entity);
+ static void Add(CEntity *entity);
+
+ static CSector *GetSector(int x, int y) { return &ms_aSectors[y][x]; }
+ static CPtrList &GetBigBuildingList(eLevelName i) { return ms_bigBuildingsList[i]; }
+ static CPtrList &GetMovingEntityList(void) { return ms_listMovingEntityPtrs; }
+ static uint16 GetCurrentScanCode(void) { return ms_nCurrentScanCode; }
+ static void AdvanceCurrentScanCode(void){
+ if(++CWorld::ms_nCurrentScanCode == 0){
+ CWorld::ClearScanCodes();
+ CWorld::ms_nCurrentScanCode = 1;
+ }
+ }
+ static void ClearScanCodes(void);
+
+ static bool CameraToIgnoreThisObject(CEntity *ent);
+
+ static bool ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+ static bool ProcessLineOfSightSector(CSector &sector, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+ static bool ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+ static bool ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly);
+ static bool ProcessVerticalLineSector(CSector &sector, const CColLine &line, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly);
+ static bool ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly);
+ static bool GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+ static bool GetIsLineOfSightSectorClear(CSector &sector, const CColLine &line, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+ static bool GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, bool ignoreSomeObjects = false);
+
+ static float FindGroundZForCoord(float x, float y);
+ static float FindGroundZFor3DCoord(float x, float y, float z, bool *found);
+ static float FindRoofZFor3DCoord(float x, float y, float z, bool *found);
+
+ static float GetSectorX(float f) { return ((f - WORLD_MIN_X)/SECTOR_SIZE_X); }
+ static float GetSectorY(float f) { return ((f - WORLD_MIN_Y)/SECTOR_SIZE_Y); }
+ static int GetSectorIndexX(float f) { return (int)GetSectorX(f); }
+ static int GetSectorIndexY(float f) { return (int)GetSectorY(f); }
+ static float GetWorldX(int x) { return x*SECTOR_SIZE_X + WORLD_MIN_X; }
+ static float GetWorldY(int y) { return y*SECTOR_SIZE_Y + WORLD_MIN_Y; }
+};
+
+class CPlayerPed;
+class CVehicle;
+CPlayerPed *FindPlayerPed(void);
+CVehicle *FindPlayerVehicle(void);
+CVehicle *FindPlayerTrain(void);
+CEntity *FindPlayerEntity(void);
+CVector FindPlayerCoors(void);
+CVector &FindPlayerSpeed(void);
+CVector &FindPlayerCentreOfWorld(int32 player);
+CVector &FindPlayerCentreOfWorld_NoSniperShift(void);
+float FindPlayerHeading(void);
diff --git a/src/core/ZoneCull.cpp b/src/core/ZoneCull.cpp
new file mode 100644
index 00000000..90155bcf
--- /dev/null
+++ b/src/core/ZoneCull.cpp
@@ -0,0 +1,370 @@
+#include "common.h"
+#include "patcher.h"
+#include "Building.h"
+#include "Treadable.h"
+#include "Train.h"
+#include "Pools.h"
+#include "Timer.h"
+#include "Camera.h"
+#include "World.h"
+#include "FileMgr.h"
+#include "ZoneCull.h"
+
+int32 &CCullZones::NumCullZones = *(int*)0x8F2564;
+CCullZone *CCullZones::aZones = (CCullZone*)0x864750; // [NUMCULLZONES];
+int32 &CCullZones::NumAttributeZones = *(int*)0x8E29D0;
+CAttributeZone *CCullZones::aAttributeZones = (CAttributeZone*)0x709C60; // [NUMATTRIBZONES];
+uint16 *CCullZones::aIndices = (uint16*)0x847330; // [NUMZONEINDICES];
+int16 *CCullZones::aPointersToBigBuildingsForBuildings = (int16*)0x86C9D0; // [NUMBUILDINGS];
+int16 *CCullZones::aPointersToBigBuildingsForTreadables = (int16*)0x8F1B8C; // [NUMTREADABLES];
+
+int32 &CCullZones::CurrentWantedLevelDrop_Player = *(int32*)0x880DA8;
+int32 &CCullZones::CurrentFlags_Camera = *(int32*)0x940718;
+int32 &CCullZones::CurrentFlags_Player = *(int32*)0x9415F0;
+int32 &CCullZones::OldCullZone = *(int32*)0x8E2C90;
+int32 &CCullZones::EntityIndicesUsed = *(int32*)0x8F2508;
+bool &CCullZones::bCurrentSubwayIsInvisible = *(bool*)0x95CDA5;
+bool &CCullZones::bCullZonesDisabled = *(bool*)0x95CD4A;
+
+
+void
+CCullZones::Init(void)
+{
+ int i;
+
+ NumAttributeZones = 0;
+ NumCullZones = 0;
+ CurrentWantedLevelDrop_Player = 0;
+ CurrentFlags_Camera = 0;
+ CurrentFlags_Player = 0;
+ OldCullZone = -1;
+ EntityIndicesUsed = 0;
+ bCurrentSubwayIsInvisible = false;
+
+ for(i = 0; i < NUMBUILDINGS; i++)
+ aPointersToBigBuildingsForBuildings[i] = -1;
+ for(i = 0; i < NUMTREADABLES; i++)
+ aPointersToBigBuildingsForTreadables[i] = -1;
+}
+
+void
+CCullZones::ResolveVisibilities(void)
+{
+ int fd;
+
+ CFileMgr::SetDir("");
+ fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb");
+ if(fd > 0){
+ CFileMgr::Read(fd, (char*)&NumCullZones, 4);
+ CFileMgr::Read(fd, (char*)aZones, NUMCULLZONES*sizeof(CCullZone));
+ CFileMgr::Read(fd, (char*)&NumAttributeZones, 4);
+ CFileMgr::Read(fd, (char*)aAttributeZones, NUMATTRIBZONES*sizeof(CAttributeZone));
+ CFileMgr::Read(fd, (char*)aIndices, NUMZONEINDICES*2);
+ CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, NUMBUILDINGS*2);
+ CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, NUMTREADABLES*2);
+ CFileMgr::CloseFile(fd);
+ }else{
+ // TODO: implement code from mobile to generate data here
+ }
+}
+
+void
+CCullZones::Update(void)
+{
+ bool invisible;
+
+ if(bCullZonesDisabled)
+ return;
+
+ switch(CTimer::GetFrameCounter() & 7){
+ case 0:
+ case 4:
+ /* Update Cull zone */
+ ForceCullZoneCoors(TheCamera.GetGameCamPosition());
+ break;
+
+ case 2:
+ /* Update camera attributes */
+ CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil);
+ invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0;
+ if(invisible != bCurrentSubwayIsInvisible){
+ MarkSubwayAsInvisible(!invisible);
+ bCurrentSubwayIsInvisible = invisible;
+ }
+ break;
+
+ case 6:
+ /* Update player attributes */
+ CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(),
+ &CurrentWantedLevelDrop_Player);
+ break;
+ }
+}
+
+void
+CCullZones::ForceCullZoneCoors(CVector coors)
+{
+ int32 z;
+ z = FindCullZoneForCoors(coors);
+ if(z != OldCullZone){
+ if(OldCullZone >= 0)
+ aZones[OldCullZone].DoStuffLeavingZone();
+ if(z >= 0)
+ aZones[z].DoStuffEnteringZone();
+ OldCullZone = z;
+ }
+}
+
+int32
+CCullZones::FindCullZoneForCoors(CVector coors)
+{
+ int i;
+
+ for(i = 0; i < NumCullZones; i++)
+ if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx &&
+ coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy &&
+ coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz)
+ return i;
+ return -1;
+}
+
+int32
+CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel)
+{
+ int i;
+ int32 attribs;
+
+ attribs = 0;
+ for(i = 0; i < NumAttributeZones; i++)
+ if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
+ coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
+ coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){
+ attribs |= aAttributeZones[i].attributes;
+ if(wantedLevel && *wantedLevel <= aAttributeZones[i].wantedLevel)
+ *wantedLevel = aAttributeZones[i].wantedLevel;
+ }
+ return attribs;
+}
+
+CAttributeZone*
+CCullZones::FindZoneWithStairsAttributeForPlayer(void)
+{
+ int i;
+ CVector coors;
+
+ coors = FindPlayerCoors();
+ for(i = 0; i < NumAttributeZones; i++)
+ if(aAttributeZones[i].attributes & ATTRZONE_STAIRS &&
+ coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
+ coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
+ coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz)
+ return &aAttributeZones[i];
+ return nil;
+}
+
+void
+CCullZones::MarkSubwayAsInvisible(bool visible)
+{
+ int i, n;
+ CEntity *e;
+ CVehicle *v;
+
+ n = CPools::GetBuildingPool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetBuildingPool()->GetSlot(i);
+ if(e && e->bIsSubway)
+ e->bIsVisible = visible;
+ }
+
+ n = CPools::GetTreadablePool()->GetSize();
+ for(i = 0; i < n; i++){
+ e = CPools::GetTreadablePool()->GetSlot(i);
+ if(e && e->bIsSubway)
+ e->bIsVisible = visible;
+ }
+
+ n = CPools::GetVehiclePool()->GetSize();
+ for(i = 0; i < n; i++){
+ v = CPools::GetVehiclePool()->GetSlot(i);
+ if(v && v->IsTrain() && ((CTrain*)v)->m_trackId != 0)
+ v->bIsVisible = visible;
+ }
+}
+
+void
+CCullZones::AddCullZone(CVector const &position,
+ float minx, float maxx,
+ float miny, float maxy,
+ float minz, float maxz,
+ uint16 flag, int16 wantedLevel)
+{
+ CCullZone *cull;
+ CAttributeZone *attrib;
+
+ CVector v;
+ if((flag & ATTRZONE_NOTCULLZONE) == 0){
+ cull = &aZones[NumCullZones++];
+ v = position;
+ // WTF is this?
+ if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f)
+ v = CVector(1061.7f, -613.0f, 19.0f);
+ if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f)
+ v = CVector(1061.4f, -506.0f, 18.5f);
+ cull->position.x = clamp(v.x, minx, maxx);
+ cull->position.y = clamp(v.y, miny, maxy);
+ cull->position.z = clamp(v.z, minz, maxz);
+ cull->minx = minx;
+ cull->maxx = maxx;
+ cull->miny = miny;
+ cull->maxy = maxy;
+ cull->minz = minz;
+ cull->maxz = maxz;
+ cull->unk2 = 0;
+ cull->unk3 = 0;
+ cull->unk4 = 0;
+ cull->m_indexStart = 0;
+ }
+ if(flag & ~ATTRZONE_NOTCULLZONE){
+ attrib = &aAttributeZones[NumAttributeZones++];
+ attrib->minx = minx;
+ attrib->maxx = maxx;
+ attrib->miny = miny;
+ attrib->maxy = maxy;
+ attrib->minz = minz;
+ attrib->maxz = maxz;
+ attrib->attributes = flag;
+ attrib->wantedLevel = wantedLevel;
+ }
+}
+
+
+
+void
+CCullZone::DoStuffLeavingZone(void)
+{
+ int i;
+
+ for(i = 0; i < m_numBuildings; i++)
+ DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
+ for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++)
+ DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]);
+}
+
+void
+CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i)
+{
+ int16 bb;
+ int j;
+
+ if(i < 6000){
+ CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false;
+ bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
+ }else{
+ i -= 6000;
+ for(j = 0; j < 3; j++)
+ DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
+ }
+}
+
+void
+CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i)
+{
+ int16 bb;
+ int j;
+
+ if(i < 6000){
+ CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = false;
+ CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = false;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
+ }else{
+ i -= 6000;
+ for(j = 0; j < 3; j++)
+ DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]);
+ }
+}
+
+void
+CCullZone::DoStuffEnteringZone(void)
+{
+ int i;
+
+ for(i = 0; i < m_numBuildings; i++)
+ DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
+ for(; i < m_numBuildings + m_numTreadablesPlus10m; i++)
+ DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]);
+ for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++)
+ DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]);
+}
+
+void
+CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i)
+{
+ int16 bb;
+ int j;
+
+ if(i < 6000){
+ CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true;
+ bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
+ }else{
+ i -= 6000;
+ for(j = 0; j < 3; j++)
+ DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
+ }
+}
+
+void
+CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i)
+{
+ int16 bb;
+ int j;
+
+ if(i < 6000){
+ CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;;
+ CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = true;;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
+ }else{
+ i -= 6000;
+ for(j = 0; j < 3; j++)
+ DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
+ }
+}
+
+void
+CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i)
+{
+ int16 bb;
+ int j;
+
+ if(i < 6000){
+ CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
+ }else{
+ i -= 6000;
+ for(j = 0; j < 3; j++)
+ DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x524BC0, &CCullZones::Init, PATCH_JUMP);
+ InjectHook(0x524EC0, &CCullZones::ResolveVisibilities, PATCH_JUMP);
+ InjectHook(0x524F80, &CCullZones::Update, PATCH_JUMP);
+ InjectHook(0x525370, &CCullZones::AddCullZone, PATCH_JUMP);
+ InjectHook(0x5250D0, &CCullZones::ForceCullZoneCoors, PATCH_JUMP);
+ InjectHook(0x525130, &CCullZones::FindCullZoneForCoors, PATCH_JUMP);
+ InjectHook(0x5251C0, &CCullZones::FindAttributesForCoors, PATCH_JUMP);
+ InjectHook(0x525290, &CCullZones::FindZoneWithStairsAttributeForPlayer, PATCH_JUMP);
+
+ InjectHook(0x525610, &CCullZone::DoStuffLeavingZone, PATCH_JUMP);
+ InjectHook(0x525810, &CCullZone::DoStuffEnteringZone, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/ZoneCull.h b/src/core/ZoneCull.h
new file mode 100644
index 00000000..5b04b4f9
--- /dev/null
+++ b/src/core/ZoneCull.h
@@ -0,0 +1,94 @@
+class CCullZone
+{
+public:
+ CVector position;
+ float minx;
+ float maxx;
+ float miny;
+ float maxy;
+ float minz;
+ float maxz;
+
+ // TODO: figure these out:
+ int32 m_indexStart;
+ int16 unk2;
+ int16 unk3;
+ int16 unk4;
+ int16 m_numBuildings;
+ int16 m_numTreadablesPlus10m;
+ int16 m_numTreadables;
+
+ void DoStuffLeavingZone(void);
+ static void DoStuffLeavingZone_OneBuilding(uint16 i);
+ static void DoStuffLeavingZone_OneTreadableBoth(uint16 i);
+ void DoStuffEnteringZone(void);
+ static void DoStuffEnteringZone_OneBuilding(uint16 i);
+ static void DoStuffEnteringZone_OneTreadablePlus10m(uint16 i);
+ static void DoStuffEnteringZone_OneTreadable(uint16 i);
+};
+
+enum eZoneAttribs
+{
+ ATTRZONE_CAMCLOSEIN = 1,
+ ATTRZONE_STAIRS = 2,
+ ATTRZONE_1STPERSON = 4,
+ ATTRZONE_NORAIN = 8,
+ ATTRZONE_NOPOLICE = 0x10,
+ ATTRZONE_NOTCULLZONE = 0x20,
+ ATTRZONE_DOINEEDCOLLISION = 0x40,
+ ATTRZONE_SUBWAYVISIBLE = 0x80,
+};
+
+struct CAttributeZone
+{
+ float minx;
+ float maxx;
+ float miny;
+ float maxy;
+ float minz;
+ float maxz;
+ int16 attributes;
+ int16 wantedLevel;
+};
+
+class CCullZones
+{
+public:
+ static int32 &NumCullZones;
+ static CCullZone *aZones; // [NUMCULLZONES];
+ static int32 &NumAttributeZones;
+ static CAttributeZone *aAttributeZones; // [NUMATTRIBZONES];
+ static uint16 *aIndices; // [NUMZONEINDICES];
+ static int16 *aPointersToBigBuildingsForBuildings; // [NUMBUILDINGS];
+ static int16 *aPointersToBigBuildingsForTreadables; // [NUMTREADABLES];
+
+ static int32 &CurrentWantedLevelDrop_Player;
+ static int32 &CurrentFlags_Camera;
+ static int32 &CurrentFlags_Player;
+ static int32 &OldCullZone;
+ static int32 &EntityIndicesUsed;
+ static bool &bCurrentSubwayIsInvisible;
+ static bool &bCullZonesDisabled;
+
+ static void Init(void);
+ static void ResolveVisibilities(void);
+ static void Update(void);
+ static void ForceCullZoneCoors(CVector coors);
+ static int32 FindCullZoneForCoors(CVector coors);
+ static int32 FindAttributesForCoors(CVector coors, int32 *wantedLevel);
+ static CAttributeZone *FindZoneWithStairsAttributeForPlayer(void);
+ static void MarkSubwayAsInvisible(bool visible);
+ static void AddCullZone(CVector const &position,
+ float minx, float maxx,
+ float miny, float maxy,
+ float minz, float maxz,
+ uint16 flag, int16 wantedLevel);
+ static bool CamCloseInForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_CAMCLOSEIN) != 0; }
+ static bool CamStairsForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_STAIRS) != 0; }
+ static bool Cam1stPersonForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_1STPERSON) != 0; }
+ static bool NoPolice(void) { return (CurrentFlags_Player & ATTRZONE_NOPOLICE) != 0; }
+ static bool DoINeedToLoadCollision(void) { return (CurrentFlags_Player & ATTRZONE_DOINEEDCOLLISION) != 0; }
+ static bool PlayerNoRain(void) { return (CurrentFlags_Player & ATTRZONE_NORAIN) != 0; }
+ static bool CamNoRain(void) { return (CurrentFlags_Camera & ATTRZONE_NORAIN) != 0; }
+ static int32 GetWantedLevelDrop(void) { return CurrentWantedLevelDrop_Player; }
+};
diff --git a/src/core/Zones.cpp b/src/core/Zones.cpp
new file mode 100644
index 00000000..363fc3d9
--- /dev/null
+++ b/src/core/Zones.cpp
@@ -0,0 +1,874 @@
+#include "common.h"
+#include "patcher.h"
+
+#include "Zones.h"
+
+#include "Clock.h"
+#include "Text.h"
+#include "World.h"
+
+eLevelName &CTheZones::m_CurrLevel = *(eLevelName*)0x8F2BC8;
+CZone *&CTheZones::m_pPlayersZone = *(CZone**)0x8F254C;
+int16 &CTheZones::FindIndex = *(int16*)0x95CC40;
+
+uint16 &CTheZones::NumberOfAudioZones = *(uint16*)0x95CC84;
+int16 *CTheZones::AudioZoneArray = (int16*)0x713BC0;
+uint16 &CTheZones::TotalNumberOfMapZones = *(uint16*)0x95CC74;
+uint16 &CTheZones::TotalNumberOfZones = *(uint16*)0x95CC36;
+CZone *CTheZones::ZoneArray = (CZone*)0x86BEE0;
+CZone *CTheZones::MapZoneArray = (CZone*)0x663EC0;
+uint16 &CTheZones::TotalNumberOfZoneInfos = *(uint16*)0x95CC3C;
+CZoneInfo *CTheZones::ZoneInfoArray = (CZoneInfo*)0x714400;
+
+#define SWAPF(a, b) { float t; t = a; a = b; b = t; }
+
+static void
+CheckZoneInfo(CZoneInfo *info)
+{
+ assert(info->carThreshold[0] >= 0);
+ assert(info->carThreshold[0] <= info->carThreshold[1]);
+ assert(info->carThreshold[1] <= info->carThreshold[2]);
+ assert(info->carThreshold[2] <= info->carThreshold[3]);
+ assert(info->carThreshold[3] <= info->carThreshold[4]);
+ assert(info->carThreshold[4] <= info->carThreshold[5]);
+ assert(info->carThreshold[5] <= info->copThreshold);
+ assert(info->copThreshold <= info->gangThreshold[0]);
+ assert(info->gangThreshold[0] <= info->gangThreshold[1]);
+ assert(info->gangThreshold[1] <= info->gangThreshold[2]);
+ assert(info->gangThreshold[2] <= info->gangThreshold[3]);
+ assert(info->gangThreshold[3] <= info->gangThreshold[4]);
+ assert(info->gangThreshold[4] <= info->gangThreshold[5]);
+ assert(info->gangThreshold[5] <= info->gangThreshold[6]);
+ assert(info->gangThreshold[6] <= info->gangThreshold[7]);
+ assert(info->gangThreshold[7] <= info->gangThreshold[8]);
+}
+
+wchar*
+CZone::GetTranslatedName(void)
+{
+ return TheText.Get(name);
+}
+
+void
+CTheZones::Init(void)
+{
+ int i;
+ for(i = 0; i < NUMAUDIOZONES; i++)
+ AudioZoneArray[i] = -1;
+ NumberOfAudioZones = 0;
+
+ CZoneInfo *zonei;
+ int x = 1000/6;
+ for(i = 0; i < 2*NUMZONES; i++){
+ zonei = &ZoneInfoArray[i];
+ zonei->carDensity = 10;
+ zonei->carThreshold[0] = x;
+ zonei->carThreshold[1] = zonei->carThreshold[0] + x;
+ zonei->carThreshold[2] = zonei->carThreshold[1] + x;
+ zonei->carThreshold[3] = zonei->carThreshold[2] + x;
+ zonei->carThreshold[4] = zonei->carThreshold[3];
+ zonei->carThreshold[5] = zonei->carThreshold[4];
+ zonei->copThreshold = zonei->carThreshold[5] + x;
+ zonei->gangThreshold[0] = zonei->copThreshold;
+ zonei->gangThreshold[1] = zonei->gangThreshold[0];
+ zonei->gangThreshold[2] = zonei->gangThreshold[1];
+ zonei->gangThreshold[3] = zonei->gangThreshold[2];
+ zonei->gangThreshold[4] = zonei->gangThreshold[3];
+ zonei->gangThreshold[5] = zonei->gangThreshold[4];
+ zonei->gangThreshold[6] = zonei->gangThreshold[5];
+ zonei->gangThreshold[7] = zonei->gangThreshold[6];
+ zonei->gangThreshold[8] = zonei->gangThreshold[7];
+ CheckZoneInfo(zonei);
+ }
+ TotalNumberOfZoneInfos = 1; // why 1?
+
+ for(i = 0; i < NUMZONES; i++)
+ memset(&ZoneArray[i], 0, sizeof(CZone));
+ strcpy(ZoneArray[0].name, "CITYZON");
+ ZoneArray[0].minx = -4000.0f;
+ ZoneArray[0].miny = -4000.0f;
+ ZoneArray[0].minz = -500.0f;
+ ZoneArray[0].maxx = 4000.0f;
+ ZoneArray[0].maxy = 4000.0f;
+ ZoneArray[0].maxz = 500.0f;
+ ZoneArray[0].level = LEVEL_NONE;
+ TotalNumberOfZones = 1;
+
+ m_CurrLevel = LEVEL_NONE;
+ m_pPlayersZone = &ZoneArray[0];
+
+ for(i = 0; i < NUMMAPZONES; i++){
+ memset(&MapZoneArray[i], 0, sizeof(CZone));
+ MapZoneArray[i].type = ZONE_MAPZONE;
+ }
+ strcpy(MapZoneArray[0].name, "THEMAP");
+ MapZoneArray[0].minx = -4000.0f;
+ MapZoneArray[0].miny = -4000.0f;
+ MapZoneArray[0].minz = -500.0f;
+ MapZoneArray[0].maxx = 4000.0f;
+ MapZoneArray[0].maxy = 4000.0f;
+ MapZoneArray[0].maxz = 500.0f;
+ MapZoneArray[0].level = LEVEL_NONE;
+ TotalNumberOfMapZones = 1;
+}
+
+void
+CTheZones::Update(void)
+{
+ CVector pos;
+ pos = FindPlayerCoors();
+ m_pPlayersZone = FindSmallestZonePosition(&pos);
+ m_CurrLevel = GetLevelFromPosition(pos);
+}
+
+void
+CTheZones::CreateZone(char *name, eZoneType type,
+ float minx, float miny, float minz,
+ float maxx, float maxy, float maxz,
+ eLevelName level)
+{
+ CZone *zone;
+ char *p;
+
+ if(minx > maxx) SWAPF(minx, maxx);
+ if(miny > maxy) SWAPF(miny, maxy);
+ if(minz > maxz) SWAPF(minz, maxz);
+
+ // make upper case
+ for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p);
+
+ // add zone
+ zone = &ZoneArray[TotalNumberOfZones++];
+ strncpy(zone->name, name, 7);
+ zone->name[7] = '\0';
+ zone->type = type;
+ zone->minx = minx;
+ zone->miny = miny;
+ zone->minz = minz;
+ zone->maxx = maxx;
+ zone->maxy = maxy;
+ zone->maxz = maxz;
+ zone->level = level;
+ if(type == ZONE_AUDIO || type == ZONE_TYPE1 || type == ZONE_TYPE2){
+ zone->zoneinfoDay = TotalNumberOfZoneInfos++;
+ zone->zoneinfoNight = TotalNumberOfZoneInfos++;
+ }
+}
+
+void
+CTheZones::CreateMapZone(char *name, eZoneType type,
+ float minx, float miny, float minz,
+ float maxx, float maxy, float maxz,
+ eLevelName level)
+{
+ CZone *zone;
+ char *p;
+
+ if(minx > maxx) SWAPF(minx, maxx);
+ if(miny > maxy) SWAPF(miny, maxy);
+ if(minz > maxz) SWAPF(minz, maxz);
+
+ // make upper case
+ for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p);
+
+ // add zone
+ zone = &MapZoneArray[TotalNumberOfMapZones++];
+ strncpy(zone->name, name, 7);
+ zone->name[7] = '\0';
+ zone->type = type;
+ zone->minx = minx;
+ zone->miny = miny;
+ zone->minz = minz;
+ zone->maxx = maxx;
+ zone->maxy = maxy;
+ zone->maxz = maxz;
+ zone->level = level;
+}
+
+void
+CTheZones::PostZoneCreation(void)
+{
+ int i;
+ for(i = 1; i < TotalNumberOfZones; i++)
+ InsertZoneIntoZoneHierarchy(&ZoneArray[i]);
+ InitialiseAudioZoneArray();
+}
+
+void
+CTheZones::InsertZoneIntoZoneHierarchy(CZone *zone)
+{
+ zone->child = nil;
+ zone->parent = nil;
+ zone->next = nil;
+ InsertZoneIntoZoneHierRecursive(zone, &ZoneArray[0]);
+}
+
+bool
+CTheZones::InsertZoneIntoZoneHierRecursive(CZone *inner, CZone *outer)
+{
+ int n;
+ CZone *child, *next, *insert;
+
+ // return false if inner was not inserted into outer
+ if(outer == nil ||
+ !ZoneIsEntirelyContainedWithinOtherZone(inner, outer))
+ return false;
+
+ // try to insert inner into children of outer
+ for(child = outer->child; child; child = child->next)
+ if(InsertZoneIntoZoneHierRecursive(inner, child))
+ return true;
+
+ // insert inner as child of outer
+ // count number of outer's children contained within inner
+ n = 0;
+ for(child = outer->child; child; child = child->next)
+ if(ZoneIsEntirelyContainedWithinOtherZone(child, inner))
+ n++;
+ inner->next = outer->child;
+ inner->parent = outer;
+ outer->child = inner;
+ // move children from outer to inner
+ if(n){
+ insert = inner;
+ for(child = inner->next; child; child = next){
+ next = child->next;
+ if(ZoneIsEntirelyContainedWithinOtherZone(child,inner)){
+ insert->next = child->next;
+ child->parent = inner;
+ child->next = inner->child;
+ inner->child = child;
+ }else
+ insert = child;
+ }
+ }
+
+ return true;
+}
+
+bool
+CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone *inner, CZone *outer)
+{
+ char tmp[100];
+
+ if(inner->minx < outer->minx ||
+ inner->maxx > outer->maxx ||
+ inner->miny < outer->miny ||
+ inner->maxy > outer->maxy ||
+ inner->minz < outer->minz ||
+ inner->maxz > outer->maxz){
+ CVector vmin(inner->minx, inner->miny, inner->minz);
+ if(PointLiesWithinZone(vmin, outer))
+ sprintf(tmp, "Overlapping zones %s and %s\n",
+ inner->name, outer->name);
+ CVector vmax(inner->maxx, inner->maxy, inner->maxz);
+ if(PointLiesWithinZone(vmax, outer))
+ sprintf(tmp, "Overlapping zones %s and %s\n",
+ inner->name, outer->name);
+ return false;
+ }
+ return true;
+}
+
+bool
+CTheZones::PointLiesWithinZone(const CVector &v, CZone *zone)
+{
+ return zone->minx <= v.x && v.x <= zone->maxx &&
+ zone->miny <= v.y && v.y <= zone->maxy &&
+ zone->minz <= v.z && v.z <= zone->maxz;
+}
+
+eLevelName
+CTheZones::GetLevelFromPosition(CVector const &v)
+{
+ int i;
+// char tmp[116];
+// if(!PointLiesWithinZone(v, &MapZoneArray[0]))
+// sprintf(tmp, "x = %.3f y= %.3f z = %.3f\n", v.x, v.y, v.z);
+ for(i = 1; i < TotalNumberOfMapZones; i++)
+ if(PointLiesWithinZone(v, &MapZoneArray[i]))
+ return MapZoneArray[i].level;
+ return MapZoneArray[0].level;
+}
+
+CZone*
+CTheZones::FindSmallestZonePosition(const CVector *v)
+{
+ CZone *best = &ZoneArray[0];
+ // zone to test next
+ CZone *zone = ZoneArray[0].child;
+ while(zone)
+ // if in zone, descent into children
+ if(PointLiesWithinZone(*v, zone)){
+ best = zone;
+ zone = zone->child;
+ // otherwise try next zone
+ }else
+ zone = zone->next;
+ return best;
+}
+
+CZone*
+CTheZones::FindSmallestZonePositionType(const CVector *v, eZoneType type)
+{
+ CZone *best = nil;
+ if(ZoneArray[0].type == type)
+ best = &ZoneArray[0];
+ // zone to test next
+ CZone *zone = ZoneArray[0].child;
+ while(zone)
+ // if in zone, descent into children
+ if(PointLiesWithinZone(*v, zone)){
+ if(zone->type == type)
+ best = zone;
+ zone = zone->child;
+ // otherwise try next zone
+ }else
+ zone = zone->next;
+ return best;
+}
+
+CZone*
+CTheZones::FindSmallestZonePositionILN(const CVector *v)
+{
+ CZone *best = nil;
+ if(ZoneArray[0].type == ZONE_AUDIO ||
+ ZoneArray[0].type == ZONE_TYPE1 ||
+ ZoneArray[0].type == ZONE_TYPE2)
+ best = &ZoneArray[0];
+ // zone to test next
+ CZone *zone = ZoneArray[0].child;
+ while(zone)
+ // if in zone, descent into children
+ if(PointLiesWithinZone(*v, zone)){
+ if(zone->type == ZONE_AUDIO ||
+ zone->type == ZONE_TYPE1 ||
+ zone->type == ZONE_TYPE2)
+ best = zone;
+ zone = zone->child;
+ // otherwise try next zone
+ }else
+ zone = zone->next;
+ return best;
+}
+
+int16
+CTheZones::FindZoneByLabelAndReturnIndex(char *name)
+{
+ char str[8];
+ memset(str, 0, 8);
+ strncpy(str, name, 8);
+ for(FindIndex = 0; FindIndex < TotalNumberOfZones; FindIndex++)
+ if(strcmp(GetZone(FindIndex)->name, name) == 0)
+ return FindIndex;
+ return -1;
+}
+
+CZoneInfo*
+CTheZones::GetZoneInfo(const CVector *v, uint8 day)
+{
+ CZone *zone;
+ zone = FindSmallestZonePositionILN(v);
+ if(zone == nil)
+ return &ZoneInfoArray[0];
+ return &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight];
+}
+
+void
+CTheZones::GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info)
+{
+ CZoneInfo *day, *night;
+ float d, n;
+
+ day = GetZoneInfo(pos, 1);
+ night = GetZoneInfo(pos, 0);
+
+ if(CClock::GetIsTimeInRange(8, 19))
+ *info = *day;
+ else if(CClock::GetIsTimeInRange(22, 5))
+ *info = *night;
+ else{
+ if(CClock::GetIsTimeInRange(19, 22)){
+ n = (CClock::GetHours() - 19) / 3.0f;
+ assert(n >= 0.0f && n <= 1.0f);
+ d = 1.0f - n;
+ }else{
+ d = (CClock::GetHours() - 5) / 3.0f;
+ assert(d >= 0.0f && d <= 1.0f);
+ n = 1.0f - d;
+ }
+ info->carDensity = day->carDensity * d + night->carDensity * n;
+ info->carThreshold[0] = day->carThreshold[0] * d + night->carThreshold[0] * n;
+ info->carThreshold[1] = day->carThreshold[1] * d + night->carThreshold[1] * n;
+ info->carThreshold[2] = day->carThreshold[2] * d + night->carThreshold[2] * n;
+ info->carThreshold[3] = day->carThreshold[3] * d + night->carThreshold[3] * n;
+ info->carThreshold[4] = day->carThreshold[4] * d + night->carThreshold[4] * n;
+ info->carThreshold[5] = day->carThreshold[5] * d + night->carThreshold[5] * n;
+ info->copThreshold = day->copThreshold * d + night->copThreshold * n;
+ info->gangThreshold[0] = day->gangThreshold[0] * d + night->gangThreshold[0] * n;
+ info->gangThreshold[1] = day->gangThreshold[1] * d + night->gangThreshold[1] * n;
+ info->gangThreshold[2] = day->gangThreshold[2] * d + night->gangThreshold[2] * n;
+ info->gangThreshold[3] = day->gangThreshold[3] * d + night->gangThreshold[3] * n;
+ info->gangThreshold[4] = day->gangThreshold[4] * d + night->gangThreshold[4] * n;
+ info->gangThreshold[5] = day->gangThreshold[5] * d + night->gangThreshold[5] * n;
+ info->gangThreshold[6] = day->gangThreshold[6] * d + night->gangThreshold[6] * n;
+ info->gangThreshold[7] = day->gangThreshold[7] * d + night->gangThreshold[7] * n;
+ info->gangThreshold[8] = day->gangThreshold[8] * d + night->gangThreshold[8] * n;
+
+ info->pedDensity = day->pedDensity * d + night->pedDensity * n;
+ info->copDensity = day->copDensity * d + night->copDensity * n;
+ info->gangDensity[0] = day->gangDensity[0] * d + night->gangDensity[0] * n;
+ info->gangDensity[1] = day->gangDensity[1] * d + night->gangDensity[1] * n;
+ info->gangDensity[2] = day->gangDensity[2] * d + night->gangDensity[2] * n;
+ info->gangDensity[3] = day->gangDensity[3] * d + night->gangDensity[3] * n;
+ info->gangDensity[4] = day->gangDensity[4] * d + night->gangDensity[4] * n;
+ info->gangDensity[5] = day->gangDensity[5] * d + night->gangDensity[5] * n;
+ info->gangDensity[6] = day->gangDensity[6] * d + night->gangDensity[6] * n;
+ info->gangDensity[7] = day->gangDensity[7] * d + night->gangDensity[7] * n;
+ info->gangDensity[8] = day->gangDensity[8] * d + night->gangDensity[8] * n;
+ }
+ if(CClock::GetIsTimeInRange(5, 19))
+ info->pedGroup = day->pedGroup;
+ else
+ info->pedGroup = night->pedGroup;
+
+ CheckZoneInfo(info);
+}
+
+void
+CTheZones::SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity,
+ int16 gang0Num, int16 gang1Num, int16 gang2Num,
+ int16 gang3Num, int16 gang4Num, int16 gang5Num,
+ int16 gang6Num, int16 gang7Num, int16 gang8Num,
+ int16 copNum,
+ int16 car0Num, int16 car1Num, int16 car2Num,
+ int16 car3Num, int16 car4Num, int16 car5Num)
+{
+ CZone *zone;
+ CZoneInfo *info;
+ zone = GetZone(zoneid);
+ info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight];
+
+ CheckZoneInfo(info);
+
+ if(carDensity != -1) info->carDensity = carDensity;
+ int16 oldCar1Num = info->carThreshold[1] - info->carThreshold[0];
+ int16 oldCar2Num = info->carThreshold[2] - info->carThreshold[1];
+ int16 oldCar3Num = info->carThreshold[3] - info->carThreshold[2];
+ int16 oldCar4Num = info->carThreshold[4] - info->carThreshold[3];
+ int16 oldCar5Num = info->carThreshold[5] - info->carThreshold[4];
+ int16 oldCopNum = info->copThreshold - info->carThreshold[5];
+ int16 oldGang0Num = info->gangThreshold[0] - info->copThreshold;
+ int16 oldGang1Num = info->gangThreshold[1] - info->gangThreshold[0];
+ int16 oldGang2Num = info->gangThreshold[2] - info->gangThreshold[1];
+ int16 oldGang3Num = info->gangThreshold[3] - info->gangThreshold[2];
+ int16 oldGang4Num = info->gangThreshold[4] - info->gangThreshold[3];
+ int16 oldGang5Num = info->gangThreshold[5] - info->gangThreshold[4];
+ int16 oldGang6Num = info->gangThreshold[6] - info->gangThreshold[5];
+ int16 oldGang7Num = info->gangThreshold[7] - info->gangThreshold[6];
+ int16 oldGang8Num = info->gangThreshold[8] - info->gangThreshold[7];
+
+ if(car0Num != -1) info->carThreshold[0] = car0Num;
+ if(car1Num != -1) info->carThreshold[1] = info->carThreshold[0] + car1Num;
+ else info->carThreshold[1] = info->carThreshold[0] + oldCar1Num;
+ if(car2Num != -1) info->carThreshold[2] = info->carThreshold[1] + car2Num;
+ else info->carThreshold[2] = info->carThreshold[1] + oldCar2Num;
+ if(car3Num != -1) info->carThreshold[3] = info->carThreshold[2] + car3Num;
+ else info->carThreshold[3] = info->carThreshold[2] + oldCar3Num;
+ if(car4Num != -1) info->carThreshold[4] = info->carThreshold[3] + car4Num;
+ else info->carThreshold[4] = info->carThreshold[3] + oldCar4Num;
+ if(car5Num != -1) info->carThreshold[5] = info->carThreshold[4] + car5Num;
+ else info->carThreshold[5] = info->carThreshold[4] + oldCar5Num;
+ if(copNum != -1) info->copThreshold = info->carThreshold[5] + copNum;
+ else info->copThreshold = info->carThreshold[5] + oldCopNum;
+ if(gang0Num != -1) info->gangThreshold[0] = info->copThreshold + gang0Num;
+ else info->gangThreshold[0] = info->copThreshold + oldGang0Num;
+ if(gang1Num != -1) info->gangThreshold[1] = info->gangThreshold[0] + gang1Num;
+ else info->gangThreshold[1] = info->gangThreshold[0] + oldGang1Num;
+ if(gang2Num != -1) info->gangThreshold[2] = info->gangThreshold[1] + gang2Num;
+ else info->gangThreshold[2] = info->gangThreshold[1] + oldGang2Num;
+ if(gang3Num != -1) info->gangThreshold[3] = info->gangThreshold[2] + gang3Num;
+ else info->gangThreshold[3] = info->gangThreshold[2] + oldGang3Num;
+ if(gang4Num != -1) info->gangThreshold[4] = info->gangThreshold[3] + gang4Num;
+ else info->gangThreshold[4] = info->gangThreshold[3] + oldGang4Num;
+ if(gang5Num != -1) info->gangThreshold[5] = info->gangThreshold[4] + gang5Num;
+ else info->gangThreshold[5] = info->gangThreshold[4] + oldGang5Num;
+ if(gang6Num != -1) info->gangThreshold[6] = info->gangThreshold[5] + gang6Num;
+ else info->gangThreshold[6] = info->gangThreshold[5] + oldGang6Num;
+ if(gang7Num != -1) info->gangThreshold[7] = info->gangThreshold[6] + gang7Num;
+ else info->gangThreshold[7] = info->gangThreshold[6] + oldGang7Num;
+ if(gang8Num != -1) info->gangThreshold[8] = info->gangThreshold[7] + gang8Num;
+ else info->gangThreshold[8] = info->gangThreshold[7] + oldGang8Num;
+
+ CheckZoneInfo(info);
+}
+
+void
+CTheZones::SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity,
+ int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density,
+ int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density,
+ int16 gang8Density, int16 copDensity)
+{
+ CZone *zone;
+ CZoneInfo *info;
+ zone = GetZone(zoneid);
+ info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight];
+ if(pedDensity != -1) info->pedDensity = pedDensity;
+ if(copDensity != -1) info->copDensity = copDensity;
+ if(gang0Density != -1) info->gangDensity[0] = gang0Density;
+ if(gang1Density != -1) info->gangDensity[1] = gang1Density;
+ if(gang2Density != -1) info->gangDensity[2] = gang2Density;
+ if(gang3Density != -1) info->gangDensity[3] = gang3Density;
+ if(gang4Density != -1) info->gangDensity[4] = gang4Density;
+ if(gang5Density != -1) info->gangDensity[5] = gang5Density;
+ if(gang6Density != -1) info->gangDensity[6] = gang6Density;
+ if(gang7Density != -1) info->gangDensity[7] = gang7Density;
+ if(gang8Density != -1) info->gangDensity[8] = gang8Density;
+}
+
+void
+CTheZones::SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity)
+{
+ CZone *zone;
+ zone = GetZone(zoneid);
+ if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2)
+ ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity;
+}
+
+void
+CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity)
+{
+ CZone *zone;
+ zone = GetZone(zoneid);
+ if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2)
+ ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity;
+}
+
+void
+CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup)
+{
+ CZone *zone;
+ zone = GetZone(zoneid);
+ if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2)
+ ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedGroup = pedgroup;
+}
+
+int16
+CTheZones::FindAudioZone(CVector *pos)
+{
+ int i;
+
+ for(i = 0; i < NumberOfAudioZones; i++)
+ if(PointLiesWithinZone(*pos, GetZone(AudioZoneArray[i])))
+ return i;
+ return -1;
+}
+
+eLevelName
+CTheZones::FindZoneForPoint(const CVector &pos)
+{
+ if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("IND_ZON"))))
+ return LEVEL_INDUSTRIAL;
+ if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("COM_ZON"))))
+ return LEVEL_COMMERCIAL;
+ if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("SUB_ZON"))))
+ return LEVEL_SUBURBAN;
+ return LEVEL_NONE;
+}
+
+void
+CTheZones::AddZoneToAudioZoneArray(CZone *zone)
+{
+ int i, z;
+
+ if(zone->type != ZONE_AUDIO)
+ return;
+
+ /* This is a bit stupid */
+ z = -1;
+ for(i = 0; i < NUMZONES; i++)
+ if(&ZoneArray[i] == zone)
+ z = i;
+ AudioZoneArray[NumberOfAudioZones++] = z;
+}
+
+void
+CTheZones::InitialiseAudioZoneArray(void)
+{
+ bool gonext;
+ CZone *zone;
+
+ gonext = false;
+ zone = &ZoneArray[0];
+ // Go deep first,
+ // set gonext when backing up a level to visit the next child
+ while(zone)
+ if(gonext){
+ AddZoneToAudioZoneArray(zone);
+ if(zone->next){
+ gonext = false;
+ zone = zone->next;
+ }else
+ zone = zone->parent;
+ }else if(zone->child)
+ zone = zone->child;
+ else{
+ AddZoneToAudioZoneArray(zone);
+ if(zone->next)
+ zone = zone->next;
+ else{
+ gonext = true;
+ zone = zone->parent;
+ }
+ }
+}
+
+void
+CTheZones::SaveAllZones(uint8 *buffer, uint32 *length)
+{
+ int i;
+
+ *length = 8 + 12 +
+ NUMZONES*56 + 2*NUMZONES*58 + 4 +
+ NUMMAPZONES*56 + NUMAUDIOZONES*2 + 4;
+
+ buffer[0] = 'Z';
+ buffer[1] = 'N';
+ buffer[2] = 'S';
+ buffer[3] = '\0';
+ *(uint32*)(buffer+4) = *length - 8;
+ buffer += 8;
+
+ *(int32*)(buffer) = GetIndexForZonePointer(m_pPlayersZone);
+ *(int32*)(buffer+4) = m_CurrLevel;
+ *(int16*)(buffer+8) = FindIndex;
+ *(int16*)(buffer+10) = 0;
+ buffer += 12;
+
+ for(i = 0; i < NUMZONES; i++){
+ memcpy(buffer, ZoneArray[i].name, 8);
+ *(float*)(buffer+8) = ZoneArray[i].minx;
+ *(float*)(buffer+12) = ZoneArray[i].miny;
+ *(float*)(buffer+16) = ZoneArray[i].minz;
+ *(float*)(buffer+20) = ZoneArray[i].maxx;
+ *(float*)(buffer+24) = ZoneArray[i].maxy;
+ *(float*)(buffer+28) = ZoneArray[i].maxz;
+ *(int32*)(buffer+32) = ZoneArray[i].type;
+ *(int32*)(buffer+36) = ZoneArray[i].level;
+ *(int16*)(buffer+40) = ZoneArray[i].zoneinfoDay;
+ *(int16*)(buffer+42) = ZoneArray[i].zoneinfoNight;
+ *(int32*)(buffer+44) = GetIndexForZonePointer(ZoneArray[i].child);
+ *(int32*)(buffer+48) = GetIndexForZonePointer(ZoneArray[i].parent);
+ *(int32*)(buffer+52) = GetIndexForZonePointer(ZoneArray[i].next);
+ buffer += 56;
+ }
+
+ for(i = 0; i < 2*NUMZONES; i++){
+ *(int16*)(buffer) = ZoneInfoArray[i].carDensity;
+ *(int16*)(buffer+2) = ZoneInfoArray[i].carThreshold[0];
+ *(int16*)(buffer+4) = ZoneInfoArray[i].carThreshold[1];
+ *(int16*)(buffer+6) = ZoneInfoArray[i].carThreshold[2];
+ *(int16*)(buffer+8) = ZoneInfoArray[i].carThreshold[3];
+ *(int16*)(buffer+10) = ZoneInfoArray[i].carThreshold[4];
+ *(int16*)(buffer+12) = ZoneInfoArray[i].carThreshold[5];
+ *(int16*)(buffer+14) = ZoneInfoArray[i].copThreshold;
+ *(int16*)(buffer+16) = ZoneInfoArray[i].gangThreshold[0];
+ *(int16*)(buffer+18) = ZoneInfoArray[i].gangThreshold[1];
+ *(int16*)(buffer+20) = ZoneInfoArray[i].gangThreshold[2];
+ *(int16*)(buffer+22) = ZoneInfoArray[i].gangThreshold[3];
+ *(int16*)(buffer+24) = ZoneInfoArray[i].gangThreshold[4];
+ *(int16*)(buffer+26) = ZoneInfoArray[i].gangThreshold[5];
+ *(int16*)(buffer+28) = ZoneInfoArray[i].gangThreshold[6];
+ *(int16*)(buffer+30) = ZoneInfoArray[i].gangThreshold[7];
+ *(int16*)(buffer+32) = ZoneInfoArray[i].gangThreshold[8];
+ *(uint16*)(buffer+34) = ZoneInfoArray[i].pedDensity;
+ *(uint16*)(buffer+36) = ZoneInfoArray[i].copDensity;
+ *(uint16*)(buffer+38) = ZoneInfoArray[i].gangDensity[0];
+ *(uint16*)(buffer+40) = ZoneInfoArray[i].gangDensity[1];
+ *(uint16*)(buffer+42) = ZoneInfoArray[i].gangDensity[2];
+ *(uint16*)(buffer+44) = ZoneInfoArray[i].gangDensity[3];
+ *(uint16*)(buffer+46) = ZoneInfoArray[i].gangDensity[4];
+ *(uint16*)(buffer+48) = ZoneInfoArray[i].gangDensity[5];
+ *(uint16*)(buffer+50) = ZoneInfoArray[i].gangDensity[6];
+ *(uint16*)(buffer+52) = ZoneInfoArray[i].gangDensity[7];
+ *(uint16*)(buffer+54) = ZoneInfoArray[i].gangDensity[8];
+ *(uint16*)(buffer+56) = ZoneInfoArray[i].pedGroup;
+ buffer += 58;
+ }
+
+ *(uint16*)(buffer) = TotalNumberOfZones;
+ *(uint16*)(buffer+2) = TotalNumberOfZoneInfos;
+ buffer += 4;
+
+ for(i = 0; i < NUMMAPZONES; i++){
+ memcpy(buffer, MapZoneArray[i].name, 8);
+ *(float*)(buffer+8) = MapZoneArray[i].minx;
+ *(float*)(buffer+12) = MapZoneArray[i].miny;
+ *(float*)(buffer+16) = MapZoneArray[i].minz;
+ *(float*)(buffer+20) = MapZoneArray[i].maxx;
+ *(float*)(buffer+24) = MapZoneArray[i].maxy;
+ *(float*)(buffer+28) = MapZoneArray[i].maxz;
+ *(int32*)(buffer+32) = MapZoneArray[i].type;
+ *(int32*)(buffer+36) = MapZoneArray[i].level;
+ *(int16*)(buffer+40) = MapZoneArray[i].zoneinfoDay;
+ *(int16*)(buffer+42) = MapZoneArray[i].zoneinfoNight;
+#ifdef STANDALONE
+ // BUG: GetIndexForZonePointer uses ZoneArray
+ // so indices will be unpredictable with different memory layout
+ assert(0);
+#endif
+ *(int32*)(buffer+44) = GetIndexForZonePointer(MapZoneArray[i].child);
+ *(int32*)(buffer+48) = GetIndexForZonePointer(MapZoneArray[i].parent);
+ *(int32*)(buffer+52) = GetIndexForZonePointer(MapZoneArray[i].next);
+ buffer += 56;
+ }
+
+ for(i = 0; i < NUMAUDIOZONES; i++){
+ *(int16*)buffer = AudioZoneArray[i];
+ buffer += 2;
+ }
+
+ *(uint16*)(buffer) = TotalNumberOfMapZones;
+ *(uint16*)(buffer+2) = NumberOfAudioZones;
+}
+
+void
+CTheZones::LoadAllZones(uint8 *buffer, uint32 length)
+{
+ int i;
+
+ assert(length == 8 + 12 +
+ NUMZONES*56 + 2*NUMZONES*58 + 4 +
+ NUMMAPZONES*56 + NUMAUDIOZONES*2 + 4);
+ assert(buffer[0] == 'Z');
+ assert(buffer[1] == 'N');
+ assert(buffer[2] == 'S');
+ assert(buffer[3] == '\0');
+ assert(*(uint32*)(buffer+4) == length - 8);
+ buffer += 8;
+
+ m_pPlayersZone = GetPointerForZoneIndex(*(int32*)(buffer));
+ m_CurrLevel = (eLevelName)*(int32*)(buffer+4);
+ FindIndex = *(int16*)(buffer+8);
+ assert(*(int16*)(buffer+10) == 0);
+ buffer += 12;
+
+ for(i = 0; i < NUMZONES; i++){
+ memcpy(ZoneArray[i].name, buffer, 8);
+ ZoneArray[i].minx = *(float*)(buffer+8);
+ ZoneArray[i].miny = *(float*)(buffer+12);
+ ZoneArray[i].minz = *(float*)(buffer+16);
+ ZoneArray[i].maxx = *(float*)(buffer+20);
+ ZoneArray[i].maxy = *(float*)(buffer+24);
+ ZoneArray[i].maxz = *(float*)(buffer+28);
+ ZoneArray[i].type = (eZoneType)*(int32*)(buffer+32);
+ ZoneArray[i].level = (eLevelName)*(int32*)(buffer+36);
+ ZoneArray[i].zoneinfoDay = *(int16*)(buffer+40);
+ ZoneArray[i].zoneinfoNight = *(int16*)(buffer+42);
+ ZoneArray[i].child = GetPointerForZoneIndex(*(int32*)(buffer+44));
+ ZoneArray[i].parent = GetPointerForZoneIndex(*(int32*)(buffer+48));
+ ZoneArray[i].next = GetPointerForZoneIndex(*(int32*)(buffer+52));
+ buffer += 56;
+ }
+
+ for(i = 0; i < 2*NUMZONES; i++){
+ ZoneInfoArray[i].carDensity = *(int16*)(buffer);
+ ZoneInfoArray[i].carThreshold[0] = *(int16*)(buffer+2);
+ ZoneInfoArray[i].carThreshold[1] = *(int16*)(buffer+4);
+ ZoneInfoArray[i].carThreshold[2] = *(int16*)(buffer+6);
+ ZoneInfoArray[i].carThreshold[3] = *(int16*)(buffer+8);
+ ZoneInfoArray[i].carThreshold[4] = *(int16*)(buffer+10);
+ ZoneInfoArray[i].carThreshold[5] = *(int16*)(buffer+12);
+ ZoneInfoArray[i].copThreshold = *(int16*)(buffer+14);
+ ZoneInfoArray[i].gangThreshold[0] = *(int16*)(buffer+16);
+ ZoneInfoArray[i].gangThreshold[1] = *(int16*)(buffer+18);
+ ZoneInfoArray[i].gangThreshold[2] = *(int16*)(buffer+20);
+ ZoneInfoArray[i].gangThreshold[3] = *(int16*)(buffer+22);
+ ZoneInfoArray[i].gangThreshold[4] = *(int16*)(buffer+24);
+ ZoneInfoArray[i].gangThreshold[5] = *(int16*)(buffer+26);
+ ZoneInfoArray[i].gangThreshold[6] = *(int16*)(buffer+28);
+ ZoneInfoArray[i].gangThreshold[7] = *(int16*)(buffer+30);
+ ZoneInfoArray[i].gangThreshold[8] = *(int16*)(buffer+32);
+ ZoneInfoArray[i].pedDensity = *(uint16*)(buffer+34);
+ ZoneInfoArray[i].copDensity = *(uint16*)(buffer+36);
+ ZoneInfoArray[i].gangDensity[0] = *(uint16*)(buffer+38);
+ ZoneInfoArray[i].gangDensity[1] = *(uint16*)(buffer+40);
+ ZoneInfoArray[i].gangDensity[2] = *(uint16*)(buffer+42);
+ ZoneInfoArray[i].gangDensity[3] = *(uint16*)(buffer+44);
+ ZoneInfoArray[i].gangDensity[4] = *(uint16*)(buffer+46);
+ ZoneInfoArray[i].gangDensity[5] = *(uint16*)(buffer+48);
+ ZoneInfoArray[i].gangDensity[6] = *(uint16*)(buffer+50);
+ ZoneInfoArray[i].gangDensity[7] = *(uint16*)(buffer+52);
+ ZoneInfoArray[i].gangDensity[8] = *(uint16*)(buffer+54);
+ ZoneInfoArray[i].pedGroup = *(uint16*)(buffer+56);
+ buffer += 58;
+ }
+
+ TotalNumberOfZones = *(uint16*)(buffer);
+ TotalNumberOfZoneInfos = *(uint16*)(buffer+2);
+ buffer += 4;
+
+ for(i = 0; i < NUMMAPZONES; i++){
+ memcpy(MapZoneArray[i].name, buffer, 8);
+ MapZoneArray[i].minx = *(float*)(buffer+8);
+ MapZoneArray[i].miny = *(float*)(buffer+12);
+ MapZoneArray[i].minz = *(float*)(buffer+16);
+ MapZoneArray[i].maxx = *(float*)(buffer+20);
+ MapZoneArray[i].maxy = *(float*)(buffer+24);
+ MapZoneArray[i].maxz = *(float*)(buffer+28);
+ MapZoneArray[i].type = (eZoneType)*(int32*)(buffer+32);
+ MapZoneArray[i].level = (eLevelName)*(int32*)(buffer+36);
+ MapZoneArray[i].zoneinfoDay = *(int16*)(buffer+40);
+ MapZoneArray[i].zoneinfoNight = *(int16*)(buffer+42);
+#ifdef STANDALONE
+ // BUG: GetPointerForZoneIndex uses ZoneArray
+ // so pointers will be unpredictable with different memory layout
+ assert(0);
+#endif
+ MapZoneArray[i].child = GetPointerForZoneIndex(*(int32*)(buffer+44));
+ MapZoneArray[i].parent = GetPointerForZoneIndex(*(int32*)(buffer+48));
+ MapZoneArray[i].next = GetPointerForZoneIndex(*(int32*)(buffer+52));
+ buffer += 56;
+ }
+
+ for(i = 0; i < NUMAUDIOZONES; i++){
+ AudioZoneArray[i] = *(int16*)buffer;
+ buffer += 2;
+ }
+
+ TotalNumberOfMapZones = *(uint16*)(buffer);
+ NumberOfAudioZones = *(uint16*)(buffer+2);
+}
+
+
+STARTPATCHES
+ InjectHook(0x4B5DD0, &CZone::GetTranslatedName, PATCH_JUMP);
+ InjectHook(0x4B5DE0, CTheZones::Init, PATCH_JUMP);
+ InjectHook(0x4B61D0, CTheZones::Update, PATCH_JUMP);
+ InjectHook(0x4B6210, CTheZones::CreateZone, PATCH_JUMP);
+ InjectHook(0x4B6380, CTheZones::CreateMapZone, PATCH_JUMP);
+ InjectHook(0x4B64C0, CTheZones::PostZoneCreation, PATCH_JUMP);
+ InjectHook(0x4B6500, CTheZones::InsertZoneIntoZoneHierarchy, PATCH_JUMP);
+ InjectHook(0x4B6530, CTheZones::InsertZoneIntoZoneHierRecursive, PATCH_JUMP);
+ InjectHook(0x4B65F0, CTheZones::ZoneIsEntirelyContainedWithinOtherZone, PATCH_JUMP);
+ InjectHook(0x4B6710, CTheZones::PointLiesWithinZone, PATCH_JUMP);
+ InjectHook(0x4B6910, CTheZones::GetLevelFromPosition, PATCH_JUMP);
+ InjectHook(0x4B69B0, CTheZones::FindSmallestZonePosition, PATCH_JUMP);
+ InjectHook(0x4B6790, CTheZones::FindSmallestZonePositionType, PATCH_JUMP);
+ InjectHook(0x4B6890, CTheZones::FindSmallestZonePositionILN, PATCH_JUMP);
+ InjectHook(0x4B6800, CTheZones::FindZoneByLabelAndReturnIndex, PATCH_JUMP);
+ InjectHook(0x4B6FA0, CTheZones::GetZone, PATCH_JUMP);
+ InjectHook(0x4B84F0, CTheZones::GetPointerForZoneIndex, PATCH_JUMP);
+ InjectHook(0x4B6A10, CTheZones::GetZoneInfo, PATCH_JUMP);
+ InjectHook(0x4B6FB0, CTheZones::GetZoneInfoForTimeOfDay, PATCH_JUMP);
+ InjectHook(0x4B6A50, CTheZones::SetZoneCarInfo, PATCH_JUMP);
+ InjectHook(0x4B6DC0, CTheZones::SetZonePedInfo, PATCH_JUMP);
+ InjectHook(0x4B6EB0, CTheZones::SetCarDensity, PATCH_JUMP);
+ InjectHook(0x4B6F00, CTheZones::SetPedDensity, PATCH_JUMP);
+ InjectHook(0x4B6F50, CTheZones::SetPedGroup, PATCH_JUMP);
+ InjectHook(0x4B83E0, CTheZones::FindAudioZone, PATCH_JUMP);
+ InjectHook(0x4B8430, CTheZones::FindZoneForPoint, PATCH_JUMP);
+ InjectHook(0x4B8340, CTheZones::AddZoneToAudioZoneArray, PATCH_JUMP);
+ InjectHook(0x4B8510, CTheZones::SaveAllZones, PATCH_JUMP);
+ InjectHook(0x4B8950, CTheZones::LoadAllZones, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/Zones.h b/src/core/Zones.h
new file mode 100644
index 00000000..bf3957de
--- /dev/null
+++ b/src/core/Zones.h
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "Game.h"
+
+enum eZoneType
+{
+ ZONE_AUDIO,
+ ZONE_TYPE1, // this should be NAVIG
+ ZONE_TYPE2, // this should be INFO...but all except MAPINFO get zoneinfo??
+ ZONE_MAPZONE,
+};
+
+class CZone
+{
+public:
+ char name[8];
+ float minx;
+ float miny;
+ float minz;
+ float maxx;
+ float maxy;
+ float maxz;
+ eZoneType type;
+ eLevelName level;
+ int16 zoneinfoDay;
+ int16 zoneinfoNight;
+ CZone *child;
+ CZone *parent;
+ CZone *next;
+
+ wchar *GetTranslatedName(void);
+};
+
+class CZoneInfo
+{
+public:
+ // Car data
+ int16 carDensity;
+ int16 carThreshold[6];
+ int16 copThreshold;
+ int16 gangThreshold[9];
+
+ // Ped data
+ uint16 pedDensity;
+ uint16 copDensity;
+ uint16 gangDensity[9];
+ uint16 pedGroup;
+};
+
+
+class CTheZones
+{
+public:
+ static eLevelName &m_CurrLevel;
+ static CZone *&m_pPlayersZone;
+ static int16 &FindIndex;
+
+ static uint16 &NumberOfAudioZones;
+ static int16 *AudioZoneArray; //[NUMAUDIOZONES];
+ static uint16 &TotalNumberOfMapZones;
+ static uint16 &TotalNumberOfZones;
+ static CZone *ZoneArray; //[NUMZONES];
+ static CZone *MapZoneArray; //[NUMMAPZONES];
+ static uint16 &TotalNumberOfZoneInfos;
+ static CZoneInfo *ZoneInfoArray; //[2*NUMZONES];
+
+ static void Init(void);
+ static void Update(void);
+ static void CreateZone(char *name, eZoneType type,
+ float minx, float miny, float minz,
+ float maxx, float maxy, float maxz,
+ eLevelName level);
+ static void CreateMapZone(char *name, eZoneType type,
+ float minx, float miny, float minz,
+ float maxx, float maxy, float maxz,
+ eLevelName level);
+ static CZone *GetZone(uint16 i) { return &ZoneArray[i]; }
+ static void PostZoneCreation(void);
+ static void InsertZoneIntoZoneHierarchy(CZone *zone);
+ static bool InsertZoneIntoZoneHierRecursive(CZone *z1, CZone *z2);
+ static bool ZoneIsEntirelyContainedWithinOtherZone(CZone *z1, CZone *z2);
+ static bool PointLiesWithinZone(const CVector &v, CZone *zone);
+ static eLevelName GetLevelFromPosition(CVector const &v);
+ static CZone *FindSmallestZonePosition(const CVector *v);
+ static CZone *FindSmallestZonePositionType(const CVector *v, eZoneType type);
+ static CZone *FindSmallestZonePositionILN(const CVector *v);
+ static int16 FindZoneByLabelAndReturnIndex(char *name);
+ static CZoneInfo *GetZoneInfo(const CVector *v, uint8 day);
+ static void GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info);
+ static void SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity,
+ int16 gang0Num, int16 gang1Num, int16 gang2Num,
+ int16 gang3Num, int16 gang4Num, int16 gang5Num,
+ int16 gang6Num, int16 gang7Num, int16 gang8Num,
+ int16 copNum,
+ int16 car0Num, int16 car1Num, int16 car2Num,
+ int16 car3Num, int16 car4Num, int16 car5Num);
+ static void SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity,
+ int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density,
+ int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density,
+ int16 gang8Density, int16 copDensity);
+ static void SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity);
+ static void SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity);
+ static void SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup);
+ static int16 FindAudioZone(CVector *pos);
+ static eLevelName FindZoneForPoint(const CVector &pos);
+ static CZone *GetPointerForZoneIndex(int32 i) { return i == -1 ? nil : &ZoneArray[i]; }
+ static int32 GetIndexForZonePointer(CZone *zone) { return zone == nil ? -1 : zone - ZoneArray; }
+ static void AddZoneToAudioZoneArray(CZone *zone);
+ static void InitialiseAudioZoneArray(void);
+ static void SaveAllZones(uint8 *buffer, uint32 *length);
+ static void LoadAllZones(uint8 *buffer, uint32 length);
+};
diff --git a/src/core/common.h b/src/core/common.h
new file mode 100644
index 00000000..79626acb
--- /dev/null
+++ b/src/core/common.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#define _CRT_SECURE_NO_WARNINGS
+#define _USE_MATH_DEFINES
+#pragma warning(disable: 4244) // int to float
+#pragma warning(disable: 4800) // int to bool
+#pragma warning(disable: 4305) // double to float
+#pragma warning(disable: 4838) // narrowing conversion
+#pragma warning(disable: 4996) // POSIX names
+
+#include <stdint.h>
+#include <math.h>
+//#include <assert.h>
+#include <new>
+
+#ifdef WITHD3D
+#include <windows.h>
+#include <d3d8types.h>
+#endif
+
+#include <rwcore.h>
+#include <rpworld.h>
+
+#define rwVENDORID_ROCKSTAR 0x0253F2
+
+// Get rid of bullshit windows definitions, we're not running on an 8086
+#ifdef far
+#undef far
+#endif
+#ifdef near
+#undef near
+#endif
+
+typedef uint8_t uint8;
+typedef int8_t int8;
+typedef uint16_t uint16;
+typedef int16_t int16;
+typedef uint32_t uint32;
+typedef int32_t int32;
+typedef uintptr_t uintptr;
+typedef uint64_t uint64;
+typedef int64_t int64;
+// hardcode ucs-2
+typedef uint16_t wchar;
+
+#define nil nullptr
+
+#include "config.h"
+
+#define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1))
+
+// PDP-10 like byte functions
+#define MASK(p, s) (((1<<(s))-1) << (p))
+inline uint32 dpb(uint32 b, uint32 p, uint32 s, uint32 w)
+{
+ uint32 m = MASK(p,s);
+ return w & ~m | b<<p & m;
+}
+inline uint32 ldb(uint32 p, uint32 s, uint32 w)
+{
+ return w>>p & (1<<s)-1;
+}
+
+
+// little hack
+extern void **rwengine;
+#define RwEngineInstance (*rwengine)
+
+#include "skeleton.h"
+#include "Draw.h"
+
+#define DEFAULT_SCREEN_WIDTH (640)
+#define DEFAULT_SCREEN_HEIGHT (448)
+#define DEFAULT_ASPECT_RATIO (4.0f/3.0f)
+
+// game uses maximumWidth/Height, but this probably won't work
+// with RW windowed mode
+#define SCREEN_WIDTH ((float)RsGlobal.width)
+#define SCREEN_HEIGHT ((float)RsGlobal.height)
+#define SCREEN_ASPECT_RATIO (CDraw::GetAspectRatio())
+
+// This scales from PS2 pixel coordinates to the real resolution
+#define SCREEN_STRETCH_X(a) ((a) * (float) SCREEN_WIDTH / DEFAULT_SCREEN_WIDTH)
+#define SCREEN_STRETCH_Y(a) ((a) * (float) SCREEN_HEIGHT / DEFAULT_SCREEN_HEIGHT)
+#define SCREEN_STRETCH_FROM_RIGHT(a) (SCREEN_WIDTH - SCREEN_STRETCH_X(a))
+#define SCREEN_STRETCH_FROM_BOTTOM(a) (SCREEN_HEIGHT - SCREEN_STRETCH_Y(a))
+
+// This scales from PS2 pixel coordinates while optionally maintaining the aspect ratio
+#define SCREEN_SCALE_X(a) SCREEN_SCALE_AR(SCREEN_STRETCH_X(a))
+#define SCREEN_SCALE_Y(a) SCREEN_STRETCH_Y(a)
+#define SCREEN_SCALE_FROM_RIGHT(a) (SCREEN_WIDTH - SCREEN_SCALE_X(a))
+#define SCREEN_SCALE_FROM_BOTTOM(a) (SCREEN_HEIGHT - SCREEN_SCALE_Y(a))
+
+#ifdef ASPECT_RATIO_SCALE
+#define SCREEN_SCALE_AR(a) ((a) * (4.0f / 3.0f) / SCREEN_ASPECT_RATIO)
+#else
+#define SCREEN_SCALE_AR(a) (a)
+#endif
+
+#include "math/Vector.h"
+#include "math/Vector2D.h"
+#include "math/Matrix.h"
+#include "math/Rect.h"
+
+class CRGBA
+{
+public:
+ union
+ {
+ uint32 color32;
+ struct { uint8 r, g, b, a; };
+ struct { uint8 red, green, blue, alpha; };
+#ifdef RWCORE_H
+ struct { RwRGBA rwRGBA; };
+#endif
+ };
+
+ CRGBA(void) { }
+ CRGBA(uint8 r, uint8 g, uint8 b, uint8 a) : r(r), g(g), b(b), a(a) { }
+#ifdef RWCORE_H
+ operator RwRGBA &(void) {
+ return rwRGBA;
+ }
+
+ operator RwRGBA *(void) {
+ return &rwRGBA;
+ }
+
+ operator RwRGBA (void) const {
+ return rwRGBA;
+ }
+#endif
+};
+
+#define clamp(v, low, high) ((v)<(low) ? (low) : (v)>(high) ? (high) : (v))
+
+inline float sq(float x) { return x*x; }
+#define SQR(x) ((x) * (x))
+
+#define PI M_PI
+#define DEGTORAD(x) ((x) * PI / 180.0f)
+#define RADTODEG(x) ((x) * 180.0f / PI)
+
+#ifdef USE_PS2_RAND
+#define MYRAND_MAX 65535
+#else
+#define MYRAND_MAX 32767
+#endif
+
+int myrand(void);
+void mysrand(unsigned int seed);
+
+void re3_debug(char *format, ...);
+void re3_trace(const char *filename, unsigned int lineno, const char *func, char *format, ...);
+void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func);
+
+#define DEBUGBREAK() __debugbreak();
+
+#define debug(f, ...) re3_debug("[DBG]: " f, __VA_ARGS__)
+#define DEV(f, ...) re3_debug("[DEV]: " f, __VA_ARGS__)
+#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f, __VA_ARGS__)
+#define Error(f, ...) re3_debug("[ERROR]: " f, __VA_ARGS__)
+
+#define assert(_Expression) (void)( (!!(_Expression)) || (re3_assert(#_Expression, __FILE__, __LINE__, __FUNCTION__), 0) )
+#define ASSERT assert
+
+#define _TODO(x)
+#define _TODOCONST(x) (x)
+
+#define VALIDATE_SIZE(struc, size) static_assert(sizeof(struc) == size, "Invalid structure size of " #struc)
+#define VALIDATE_OFFSET(struc, member, offset) static_assert(offsetof(struc, member) == offset, "The offset of " #member " in " #struc " is not " #offset "...")
+
+#define PERCENT(x, p) ((float(x) * (float(p) / 100.0f)))
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+#define BIT(num) (1<<(num))
+
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+#define min(a, b) (((a) < (b)) ? (a) : (b))
diff --git a/src/core/config.h b/src/core/config.h
new file mode 100644
index 00000000..8cb02190
--- /dev/null
+++ b/src/core/config.h
@@ -0,0 +1,116 @@
+#pragma once
+
+enum Config {
+ NUMCDIMAGES = 12, // gta3.img duplicates (not used on PC)
+ MAX_CDIMAGES = 8, // additional cdimages
+
+ MODELINFOSIZE = 5500,
+ TXDSTORESIZE = 850,
+ EXTRADIRSIZE = 128,
+
+ SIMPLEMODELSIZE = 5000,
+ TIMEMODELSIZE = 30,
+ CLUMPMODELSIZE = 5,
+ PEDMODELSIZE = 90,
+ VEHICLEMODELSIZE = 120,
+ TWODFXSIZE = 2000,
+
+ MAXVEHICLESLOADED = 50, // 70 on mobile
+
+ NUMOBJECTINFO = 168, // object.dat
+
+ // Pool sizes
+ NUMPTRNODES = 30000, // 26000 on PS2
+ NUMENTRYINFOS = 5400, // 3200 on PS2
+ NUMPEDS = 140, // 90 on PS2
+ NUMVEHICLES = 110, // 70 on PS2
+ NUMBUILDINGS = 5500, // 4915 on PS2
+ NUMTREADABLES = 1214,
+ NUMOBJECTS = 450,
+ NUMDUMMIES = 2802, // 2368 on PS2
+ NUMAUDIOSCRIPTOBJECTS = 256,
+
+ // Link list lengths
+ // TODO: alpha list
+ NUMCOLCACHELINKS = 200,
+ NUMREFERENCES = 800,
+
+ // Zones
+ NUMAUDIOZONES = 36,
+ NUMZONES = 50,
+ NUMMAPZONES = 25,
+
+ // Cull zones
+ NUMCULLZONES = 512,
+ NUMATTRIBZONES = 288,
+ NUMZONEINDICES = 55000,
+
+ NUMHANDLINGS = 57,
+
+ PATHNODESIZE = 4500,
+
+ NUMWEATHERS = 4,
+ NUMHOURS = 24,
+
+ NUMEXTRADIRECTIONALS = 4,
+ NUMANTENNAS = 8,
+ NUMCORONAS = 56,
+ NUMPOINTLIGHTS = 32,
+
+ NUMONSCREENTIMERENTRIES = 1,
+ NUMRADARBLIPS = 32,
+ NUMPICKUPS = 336,
+};
+
+// We'll use this once we're ready to become independent of the game
+// Use it to mark bugs in the code that will prevent the game from working then
+//#define STANDALONE
+
+// We don't expect to compile for PS2 or Xbox
+// but it might be interesting for documentation purposes
+#define GTA_PC
+//#define GTA_PS2
+//#define GTA_XBOX
+
+// This enables things from the PS2 version on PC
+#define GTA_PS2_STUFF
+
+// This is enabled for all released games.
+// any debug stuff that isn't left in any game is not in FINAL
+//#define FINAL
+
+// This is enabled for all released games except mobile
+// any debug stuff that is only left in mobile, is not in MASTER
+//#define MASTER
+
+#if defined GTA_PS2
+# define RANDOMSPLASH
+#elif defined GTA_PC
+# define GTA3_1_1_PATCH
+# ifdef GTA_PS2_STUFF
+//# define USE_PS2_RAND // this is unsafe until we have the game reversed
+# define RANDOMSPLASH // use random splash as on PS2
+# define PS2_MATFX
+# endif
+#elif defined GTA_XBOX
+#endif
+
+#ifdef MASTER
+ // only in master builds
+#else
+ // not in master builds
+#endif
+
+#ifdef FINAL
+ // in all games
+# define USE_MY_DOCUMENTS // use my documents directory for user files
+#else
+ // not in any game
+# define NASTY_GAME // nasty game for all languages
+# define NO_MOVIES // disable intro videos
+# define CHATTYSPLASH // print what the game is loading
+#endif
+
+#define FIX_BUGS // fix bugs in the game, TODO: use this more
+#define KANGAROO_CHEAT
+#define ASPECT_RATIO_SCALE
diff --git a/src/core/debugmenu_public.h b/src/core/debugmenu_public.h
new file mode 100644
index 00000000..778e7afe
--- /dev/null
+++ b/src/core/debugmenu_public.h
@@ -0,0 +1,154 @@
+
+extern "C" {
+
+typedef void (*TriggerFunc)(void);
+
+struct DebugMenuEntry;
+
+typedef DebugMenuEntry *(*DebugMenuAddInt8_TYPE)(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddInt16_TYPE)(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddInt32_TYPE)(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddInt64_TYPE)(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddUInt8_TYPE)(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddUInt16_TYPE)(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddUInt32_TYPE)(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddUInt64_TYPE)(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings);
+typedef DebugMenuEntry *(*DebugMenuAddFloat32_TYPE)(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound);
+typedef DebugMenuEntry *(*DebugMenuAddFloat64_TYPE)(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound);
+typedef DebugMenuEntry *(*DebugMenuAddCmd_TYPE)(const char *path, const char *name, TriggerFunc triggerFunc);
+typedef void (*DebugMenuEntrySetWrap_TYPE)(DebugMenuEntry *e, bool wrap);
+typedef void (*DebugMenuEntrySetStrings_TYPE)(DebugMenuEntry *e, const char **strings);
+typedef void (*DebugMenuEntrySetAddress_TYPE)(DebugMenuEntry *e, void *addr);
+
+struct DebugMenuAPI
+{
+ bool isLoaded;
+ HMODULE module;
+ DebugMenuAddInt8_TYPE addint8;
+ DebugMenuAddInt16_TYPE addint16;
+ DebugMenuAddInt32_TYPE addint32;
+ DebugMenuAddInt64_TYPE addint64;
+ DebugMenuAddUInt8_TYPE adduint8;
+ DebugMenuAddUInt16_TYPE adduint16;
+ DebugMenuAddUInt32_TYPE adduint32;
+ DebugMenuAddUInt64_TYPE adduint64;
+ DebugMenuAddFloat32_TYPE addfloat32;
+ DebugMenuAddFloat64_TYPE addfloat64;
+ DebugMenuAddCmd_TYPE addcmd;
+ DebugMenuEntrySetWrap_TYPE setwrap;
+ DebugMenuEntrySetStrings_TYPE setstrings;
+ DebugMenuEntrySetAddress_TYPE setaddress;
+};
+extern DebugMenuAPI gDebugMenuAPI;
+
+inline DebugMenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
+{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+inline DebugMenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
+{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+inline DebugMenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc)
+{ return gDebugMenuAPI.addcmd(path, name, triggerFunc); }
+inline void DebugMenuEntrySetWrap(DebugMenuEntry *e, bool wrap)
+{ gDebugMenuAPI.setwrap(e, wrap); }
+inline void DebugMenuEntrySetStrings(DebugMenuEntry *e, const char **strings)
+{ gDebugMenuAPI.setstrings(e, strings); }
+inline void DebugMenuEntrySetAddress(DebugMenuEntry *e, void *addr)
+{ gDebugMenuAPI.setaddress(e, addr); }
+
+inline bool DebugMenuLoad(void)
+{
+ if(gDebugMenuAPI.isLoaded)
+ return true;
+ HMODULE mod = LoadLibraryA("debugmenu");
+ if(mod == nil){
+ char modulePath[MAX_PATH];
+ HMODULE dllModule;
+ GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&gDebugMenuAPI, &dllModule);
+ GetModuleFileNameA(dllModule, modulePath, MAX_PATH);
+ char *p = strchr(modulePath, '\\');
+ if(p) p[1] = '\0';
+ strcat(modulePath, "debugmenu");
+ mod = LoadLibraryA(modulePath);
+ }
+ if(mod == nil)
+ return false;
+ gDebugMenuAPI.addint8 = (DebugMenuAddInt8_TYPE)GetProcAddress(mod, "DebugMenuAddInt8");
+ gDebugMenuAPI.addint16 = (DebugMenuAddInt16_TYPE)GetProcAddress(mod, "DebugMenuAddInt16");
+ gDebugMenuAPI.addint32 = (DebugMenuAddInt32_TYPE)GetProcAddress(mod, "DebugMenuAddInt32");
+ gDebugMenuAPI.addint64 = (DebugMenuAddInt64_TYPE)GetProcAddress(mod, "DebugMenuAddInt64");
+ gDebugMenuAPI.adduint8 = (DebugMenuAddUInt8_TYPE)GetProcAddress(mod, "DebugMenuAddUInt8");
+ gDebugMenuAPI.adduint16 = (DebugMenuAddUInt16_TYPE)GetProcAddress(mod, "DebugMenuAddUInt16");
+ gDebugMenuAPI.adduint32 = (DebugMenuAddUInt32_TYPE)GetProcAddress(mod, "DebugMenuAddUInt32");
+ gDebugMenuAPI.adduint64 = (DebugMenuAddUInt64_TYPE)GetProcAddress(mod, "DebugMenuAddUInt64");
+ gDebugMenuAPI.addfloat32 = (DebugMenuAddFloat32_TYPE)GetProcAddress(mod, "DebugMenuAddFloat32");
+ gDebugMenuAPI.addfloat64 = (DebugMenuAddFloat64_TYPE)GetProcAddress(mod, "DebugMenuAddFloat64");
+ gDebugMenuAPI.addcmd = (DebugMenuAddCmd_TYPE)GetProcAddress(mod, "DebugMenuAddCmd");
+ gDebugMenuAPI.setwrap = (DebugMenuEntrySetWrap_TYPE)GetProcAddress(mod, "DebugMenuEntrySetWrap");
+ gDebugMenuAPI.setstrings = (DebugMenuEntrySetStrings_TYPE)GetProcAddress(mod, "DebugMenuEntrySetStrings");
+ gDebugMenuAPI.setaddress = (DebugMenuEntrySetAddress_TYPE)GetProcAddress(mod, "DebugMenuEntrySetAddress");
+ gDebugMenuAPI.isLoaded = true;
+ gDebugMenuAPI.module = mod;
+ return true;
+}
+
+}
+
+// Also overload them for simplicity
+
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
+{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
+{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
+{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
+{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+
+inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc)
+{
+ static const char *boolstr[] = { "Off", "On" };
+ DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+ DebugMenuEntrySetWrap(e, true);
+ return e;
+}
+inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc)
+{
+ static const char *boolstr[] = { "Off", "On" };
+ DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+ DebugMenuEntrySetWrap(e, true);
+ return e;
+}
+inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc)
+{
+ static const char *boolstr[] = { "Off", "On" };
+ DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+ DebugMenuEntrySetWrap(e, true);
+ return e;
+}
diff --git a/src/core/main.cpp b/src/core/main.cpp
new file mode 100644
index 00000000..e7f42780
--- /dev/null
+++ b/src/core/main.cpp
@@ -0,0 +1,891 @@
+#include "common.h"
+#include "patcher.h"
+#include "main.h"
+#include "General.h"
+#include "RwHelper.h"
+#include "Clouds.h"
+#include "Draw.h"
+#include "Sprite2d.h"
+#include "Renderer.h"
+#include "Coronas.h"
+#include "WaterLevel.h"
+#include "Weather.h"
+#include "Glass.h"
+#include "WaterCannon.h"
+#include "SpecialFX.h"
+#include "Shadows.h"
+#include "Skidmarks.h"
+#include "Antennas.h"
+#include "Rubbish.h"
+#include "Particle.h"
+#include "Pickups.h"
+#include "WeaponEffects.h"
+#include "PointLights.h"
+#include "Fluff.h"
+#include "Replay.h"
+#include "Camera.h"
+#include "World.h"
+#include "Ped.h"
+#include "Font.h"
+#include "Pad.h"
+#include "Hud.h"
+#include "User.h"
+#include "Messages.h"
+#include "Darkel.h"
+#include "Garages.h"
+#include "MusicManager.h"
+#include "VisibilityPlugins.h"
+#include "NodeName.h"
+#include "DMAudio.h"
+#include "CutsceneMgr.h"
+#include "Lights.h"
+#include "Credits.h"
+#include "ZoneCull.h"
+#include "Timecycle.h"
+#include "TxdStore.h"
+#include "FileMgr.h"
+#include "Text.h"
+#include "RpAnimBlend.h"
+#include "Frontend.h"
+
+#define DEFAULT_VIEWWINDOW (tan(DEGTORAD(CDraw::GetFOV() * 0.5f)))
+
+
+GlobalScene &Scene = *(GlobalScene*)0x726768;
+
+uint8 work_buff[55000];
+char gString[256];
+wchar *gUString = (wchar*)0x74B018;
+
+bool &b_FoundRecentSavedGameWantToLoad = *(bool*)0x95CDA8;
+
+
+bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha);
+void DoRWStuffEndOfFrame(void);
+
+void RenderScene(void);
+void RenderDebugShit(void);
+void RenderEffects(void);
+void Render2dStuff(void);
+void RenderMenus(void);
+void DoFade(void);
+void Render2dStuffAfterFade(void);
+
+CSprite2d *LoadSplash(const char *name);
+void DestroySplashScreen(void);
+
+
+extern void (*DebugMenuProcess)(void);
+extern void (*DebugMenuRender)(void);
+void DebugMenuInit(void);
+void DebugMenuPopulate(void);
+
+void PrintGameVersion();
+
+RwRGBA gColourTop;
+
+void
+InitialiseGame(void)
+{
+ LoadingScreen(nil, nil, "loadsc0");
+ CGame::Initialise("DATA\\GTA3.DAT");
+}
+
+void
+Idle(void *arg)
+{
+#ifdef ASPECT_RATIO_SCALE
+ CDraw::SetAspectRatio(CDraw::FindAspectRatio());
+#endif
+
+ CTimer::Update();
+ CSprite2d::InitPerFrame();
+ CFont::InitPerFrame();
+ CPointLights::InitPerFrame();
+ CGame::Process();
+ DMAudio.Service();
+
+ if(CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()){
+ FrontEndMenuManager.m_bStartGameLoading = true;
+ FrontEndMenuManager.m_bLoadingSavedGame = false;
+ return;
+ }
+
+ if(FrontEndMenuManager.m_bStartGameLoading || b_FoundRecentSavedGameWantToLoad)
+ return;
+
+ SetLightsWithTimeOfDayColour(Scene.world);
+
+ if(arg == nil)
+ return;
+
+ if((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.field_452 == 1) &&
+ TheCamera.GetScreenFadeStatus() != FADE_2){
+#ifdef GTA_PC
+ // This is from SA, but it's nice for windowed mode
+ RwV2d pos;
+ pos.x = SCREEN_WIDTH/2.0f;
+ pos.y = SCREEN_HEIGHT/2.0f;
+ RsMouseSetPos(&pos);
+#endif
+ CRenderer::ConstructRenderList();
+ CRenderer::PreRender();
+
+ if(CWeather::LightningFlash && !CCullZones::CamNoRain()){
+ if(!DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255))
+ return;
+ }else{
+ if(!DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(),
+ CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(),
+ 255))
+ return;
+ }
+
+ DefinedState();
+
+ // BUG. This has to be done BEFORE RwCameraBeginUpdate
+ RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip());
+ RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart());
+
+ RenderScene();
+ RenderDebugShit();
+ RenderEffects();
+
+ if((TheCamera.m_BlurType == MBLUR_NONE || TheCamera.m_BlurType == MBLUR_NORMAL) &&
+ TheCamera.m_ScreenReductionPercentage > 0.0)
+ TheCamera.SetMotionBlurAlpha(150);
+ TheCamera.RenderMotionBlur();
+
+ Render2dStuff();
+ }else{
+ float viewWindow = DEFAULT_VIEWWINDOW;
+ CameraSize(Scene.camera, nil, viewWindow, DEFAULT_ASPECT_RATIO);
+ CVisibilityPlugins::SetRenderWareCamera(Scene.camera);
+ RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ);
+ if(!RsCameraBeginUpdate(Scene.camera))
+ return;
+ }
+
+ RenderMenus();
+#ifndef FINAL
+ PrintGameVersion();
+#endif
+ DoFade();
+ Render2dStuffAfterFade();
+ CCredits::Render();
+ DoRWStuffEndOfFrame();
+
+// if(g_SlowMode)
+// ProcessSlowMode();
+}
+
+void
+FrontendIdle(void)
+{
+#ifdef ASPECT_RATIO_SCALE
+ CDraw::SetAspectRatio(CDraw::FindAspectRatio());
+#endif
+
+ CTimer::Update();
+ CSprite2d::SetRecipNearClip();
+ CSprite2d::InitPerFrame();
+ CFont::InitPerFrame();
+ CPad::UpdatePads();
+ FrontEndMenuManager.Process();
+
+ if(RsGlobal.quit)
+ return;
+
+ float viewWindow = DEFAULT_VIEWWINDOW;
+ CameraSize(Scene.camera, nil, viewWindow, DEFAULT_ASPECT_RATIO);
+ CVisibilityPlugins::SetRenderWareCamera(Scene.camera);
+ RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ);
+ if(!RsCameraBeginUpdate(Scene.camera))
+ return;
+
+ DefinedState();
+ RenderMenus();
+#ifndef FINAL
+ PrintGameVersion();
+#endif
+ DoFade();
+ Render2dStuffAfterFade();
+ CFont::DrawFonts();
+ DoRWStuffEndOfFrame();
+}
+
+bool
+DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha)
+{
+ CRGBA TopColor(TopRed, TopGreen, TopBlue, Alpha);
+ CRGBA BottomColor(BottomRed, BottomGreen, BottomBlue, Alpha);
+
+ CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, SCREEN_ASPECT_RATIO);
+ CVisibilityPlugins::SetRenderWareCamera(Scene.camera);
+ RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ);
+
+ if(!RsCameraBeginUpdate(Scene.camera))
+ return false;
+
+ CSprite2d::InitPerFrame();
+
+ if(Alpha != 0)
+ CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), BottomColor, BottomColor, TopColor, TopColor);
+
+ return true;
+}
+
+bool
+DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha)
+{
+ CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, SCREEN_ASPECT_RATIO);
+ CVisibilityPlugins::SetRenderWareCamera(Scene.camera);
+ RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ);
+
+ if(!RsCameraBeginUpdate(Scene.camera))
+ return false;
+
+ TheCamera.m_viewMatrix.Update();
+ CClouds::RenderBackground(TopRed, TopGreen, TopBlue, BottomRed, BottomGreen, BottomBlue, Alpha);
+
+ return true;
+}
+
+void
+DoRWStuffEndOfFrame(void)
+{
+ // CDebug::DebugDisplayTextBuffer();
+ // FlushObrsPrintfs();
+ RwCameraEndUpdate(Scene.camera);
+ RsCameraShowRaster(Scene.camera);
+}
+
+
+// This is certainly a very useful function
+void
+DoRWRenderHorizon(void)
+{
+ CClouds::RenderHorizon();
+}
+
+void
+RenderScene(void)
+{
+ CClouds::Render();
+ DoRWRenderHorizon();
+ CRenderer::RenderRoads();
+ CCoronas::RenderReflections();
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
+ CRenderer::RenderEverythingBarRoads();
+ CRenderer::RenderBoats();
+ DefinedState();
+ CWaterLevel::RenderWater();
+ CRenderer::RenderFadingInEntities();
+ CRenderer::RenderVehiclesButNotBoats();
+ CWeather::RenderRainStreaks();
+}
+
+void
+RenderDebugShit(void)
+{
+ // CTheScripts::RenderTheScriptDebugLines()
+}
+
+void
+RenderEffects(void)
+{
+ CGlass::Render();
+ CWaterCannons::Render();
+ CSpecialFX::Render();
+ CShadows::RenderStaticShadows();
+ CShadows::RenderStoredShadows();
+ CSkidmarks::Render();
+ CAntennas::Render();
+ CRubbish::Render();
+ CCoronas::Render();
+ CParticle::Render();
+ CPacManPickups::Render();
+ CWeaponEffects::Render();
+ CPointLights::RenderFogEffect();
+ CMovingThings::Render();
+ CRenderer::RenderFirstPersonVehicle();
+}
+
+void
+Render2dStuff(void)
+{
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE);
+
+ CReplay::Display();
+ CPickups::RenderPickUpText();
+
+ if(TheCamera.m_WideScreenOn)
+ TheCamera.DrawBordersForWideScreen();
+
+ CPed *player = FindPlayerPed();
+ int weaponType = 0;
+ if(player)
+ weaponType = player->GetWeapon()->m_eWeaponType;
+
+ bool firstPersonWeapon = false;
+ int cammode = TheCamera.Cams[TheCamera.ActiveCam].Mode;
+ if(cammode == CCam::MODE_SNIPER ||
+ cammode == CCam::MODE_SNIPER_RUN_AROUND ||
+ cammode == CCam::MODE_ROCKET ||
+ cammode == CCam::MODE_ROCKET_RUN_AROUND)
+ firstPersonWeapon = true;
+
+ // Draw black border for sniper and rocket launcher
+ if((weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER) && firstPersonWeapon){
+ CRGBA black(0, 0, 0, 255);
+
+ // top and bottom strips
+ if (weaponType == WEAPONTYPE_ROCKETLAUNCHER) {
+ CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(180)), black);
+ CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(170), SCREEN_WIDTH, SCREEN_HEIGHT), black);
+ }
+ else {
+ CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(210)), black);
+ CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(210), SCREEN_WIDTH, SCREEN_HEIGHT), black);
+ }
+ CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH / 2 - SCREEN_SCALE_X(210), SCREEN_HEIGHT), black);
+ CSprite2d::DrawRect(CRect(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(210), 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), black);
+ }
+
+ MusicManager.DisplayRadioStationName();
+// TheConsole.Display();
+/*
+ if(CSceneEdit::m_bEditOn)
+ CSceneEdit::Draw();
+ else
+*/
+ CHud::Draw();
+ CUserDisplay::OnscnTimer.ProcessForDisplay();
+ CMessages::Display();
+ CDarkel::DrawMessages();
+ CGarages::PrintMessages();
+ CPad::PrintErrorMessage();
+ CFont::DrawFonts();
+
+ DebugMenuRender();
+}
+
+void
+RenderMenus(void)
+{
+ if(FrontEndMenuManager.m_bMenuActive)
+ FrontEndMenuManager.DrawFrontEnd();
+}
+
+bool &JustLoadedDontFadeInYet = *(bool*)0x95CDB4;
+bool &StillToFadeOut = *(bool*)0x95CD99;
+uint32 &TimeStartedCountingForFade = *(uint32*)0x9430EC;
+uint32 &TimeToStayFadedBeforeFadeOut = *(uint32*)0x611564;
+
+void
+DoFade(void)
+{
+ if(CTimer::GetIsPaused())
+ return;
+
+ if(JustLoadedDontFadeInYet){
+ JustLoadedDontFadeInYet = false;
+ TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds();
+ }
+
+ if(StillToFadeOut){
+ if(CTimer::GetTimeInMilliseconds() - TimeStartedCountingForFade > TimeToStayFadedBeforeFadeOut){
+ StillToFadeOut = false;
+ TheCamera.Fade(3.0f, FADE_IN);
+ TheCamera.ProcessFade();
+ TheCamera.ProcessMusicFade();
+ }else{
+ TheCamera.SetFadeColour(0, 0, 0);
+ TheCamera.Fade(0.0f, FADE_OUT);
+ TheCamera.ProcessFade();
+ }
+ }
+
+ if(CDraw::FadeValue != 0 || CMenuManager::m_PrefsBrightness < 256){
+ CSprite2d *splash = LoadSplash(nil);
+
+ CRGBA fadeColor;
+ CRect rect;
+ int fadeValue = CDraw::FadeValue;
+ float brightness = min(CMenuManager::m_PrefsBrightness, 256);
+ if(brightness <= 50)
+ brightness = 50;
+ if(FrontEndMenuManager.m_bMenuActive)
+ brightness = 256;
+
+ if(TheCamera.m_FadeTargetIsSplashScreen)
+ fadeValue = 0;
+
+ float fade = fadeValue + 256 - brightness;
+ if(fade == 0){
+ fadeColor.r = 0;
+ fadeColor.g = 0;
+ fadeColor.b = 0;
+ fadeColor.a = 0;
+ }else{
+ fadeColor.r = fadeValue * CDraw::FadeRed / fade;
+ fadeColor.g = fadeValue * CDraw::FadeGreen / fade;
+ fadeColor.b = fadeValue * CDraw::FadeBlue / fade;
+ int alpha = 255 - brightness*(256 - fadeValue)/256;
+ if(alpha < 0)
+ alpha = 0;
+ fadeColor.a = alpha;
+ }
+
+ if(TheCamera.m_WideScreenOn){
+ // what's this?
+ float y = SCREEN_HEIGHT/2 * TheCamera.m_ScreenReductionPercentage/100.0f;
+ rect.left = 0.0f;
+ rect.right = SCREEN_WIDTH;
+ rect.top = y - 8.0f;
+ rect.bottom = SCREEN_HEIGHT - y - 8.0f;
+ }else{
+ rect.left = 0.0f;
+ rect.right = SCREEN_WIDTH;
+ rect.top = 0.0f;
+ rect.bottom = SCREEN_HEIGHT;
+ }
+ CSprite2d::DrawRect(rect, fadeColor);
+
+ if(CDraw::FadeValue != 0 && TheCamera.m_FadeTargetIsSplashScreen){
+ fadeColor.r = 255;
+ fadeColor.g = 255;
+ fadeColor.b = 255;
+ fadeColor.a = CDraw::FadeValue;
+ splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), fadeColor, fadeColor, fadeColor, fadeColor);
+ }
+ }
+}
+
+void
+Render2dStuffAfterFade(void)
+{
+ CHud::DrawAfterFade();
+ CFont::DrawFonts();
+}
+
+CSprite2d splash;
+int splashTxdId = -1;
+
+CSprite2d*
+LoadSplash(const char *name)
+{
+ RwTexDictionary *txd;
+ char filename[140];
+ RwTexture *tex = nil;
+
+ if(name == nil)
+ return &splash;
+ if(splashTxdId == -1)
+ splashTxdId = CTxdStore::AddTxdSlot("splash");
+
+ txd = CTxdStore::GetSlot(splashTxdId)->texDict;
+ if(txd)
+ tex = RwTexDictionaryFindNamedTexture(txd, name);
+ // if texture is found, splash was already set up below
+
+ if(tex == nil){
+ CFileMgr::SetDir("TXD\\");
+ sprintf(filename, "%s.txd", name);
+ if(splash.m_pTexture)
+ splash.Delete();
+ if(txd)
+ CTxdStore::RemoveTxd(splashTxdId);
+ CTxdStore::LoadTxd(splashTxdId, filename);
+ CTxdStore::AddRef(splashTxdId);
+ CTxdStore::PushCurrentTxd();
+ CTxdStore::SetCurrentTxd(splashTxdId);
+ splash.SetTexture(name);
+ CTxdStore::PopCurrentTxd();
+ CFileMgr::SetDir("");
+ }
+
+ return &splash;
+}
+
+void
+DestroySplashScreen(void)
+{
+ splash.Delete();
+ if(splashTxdId != -1)
+ CTxdStore::RemoveTxdSlot(splashTxdId);
+ splashTxdId = -1;
+}
+
+float NumberOfChunksLoaded;
+#define TOTALNUMCHUNKS 73.0f
+
+// TODO: compare with PS2
+void
+LoadingScreen(const char *str1, const char *str2, const char *splashscreen)
+{
+ CSprite2d *splash;
+
+#ifndef RANDOMSPLASH
+ if(CGame::frenchGame || CGame::germanGame || !CGame::nastyGame)
+ splashscreen = "mainsc2";
+ else
+ splashscreen = "mainsc1";
+#endif
+
+ splash = LoadSplash(splashscreen);
+
+ if(RsGlobal.quit)
+ return;
+
+ if(DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)){
+ CSprite2d::SetRecipNearClip();
+ CSprite2d::InitPerFrame();
+ CFont::InitPerFrame();
+ DefinedState();
+ RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP);
+ splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255));
+
+ if(str1){
+ NumberOfChunksLoaded += 1;
+
+ float hpos = SCREEN_SCALE_X(40);
+ float length = SCREEN_WIDTH - SCREEN_SCALE_X(100);
+ float vpos = SCREEN_HEIGHT - SCREEN_SCALE_Y(13);
+ float height = SCREEN_SCALE_Y(7);
+ CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(40, 53, 68, 255));
+
+ length *= NumberOfChunksLoaded/TOTALNUMCHUNKS;
+ CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(81, 106, 137, 255));
+
+ // this is done by the game but is unused
+ CFont::SetScale(SCREEN_SCALE_X(2), SCREEN_SCALE_Y(2));
+ CFont::SetPropOn();
+ CFont::SetRightJustifyOn();
+ CFont::SetFontStyle(FONT_HEADING);
+
+#ifdef CHATTYSPLASH
+ // my attempt
+ static wchar tmpstr[80];
+ float scale = SCREEN_SCALE_Y(0.8f);
+ vpos -= 50*scale;
+ CFont::SetScale(scale, scale);
+ CFont::SetPropOn();
+ CFont::SetRightJustifyOff();
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::SetColor(CRGBA(255, 255, 255, 255));
+ AsciiToUnicode(str1, tmpstr);
+ CFont::PrintString(hpos, vpos, tmpstr);
+ vpos += 25*scale;
+ AsciiToUnicode(str2, tmpstr);
+ CFont::PrintString(hpos, vpos, tmpstr);
+#endif
+ }
+
+ CFont::DrawFonts();
+ DoRWStuffEndOfFrame();
+ }
+}
+
+void
+ResetLoadingScreenBar(void)
+{
+ NumberOfChunksLoaded = 0.0f;
+}
+
+void
+LoadingIslandScreen(const char *levelName)
+{
+ CSprite2d *splash;
+ wchar *name;
+ char str[100];
+ wchar wstr[80];
+ CRGBA col;
+
+ splash = LoadSplash(nil);
+ name = TheText.Get(levelName);
+ if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255))
+ return;
+
+ CSprite2d::SetRecipNearClip();
+ CSprite2d::InitPerFrame();
+ CFont::InitPerFrame();
+ DefinedState();
+ col = CRGBA(255, 255, 255, 255);
+ splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), col, col, col, col);
+ CFont::SetBackgroundOff();
+ CFont::SetScale(1.5f, 1.5f);
+ CFont::SetPropOn();
+ CFont::SetRightJustifyOn();
+ CFont::SetRightJustifyWrap(150.0f);
+ CFont::SetFontStyle(FONT_HEADING);
+ sprintf(str, "WELCOME TO");
+ AsciiToUnicode(str, wstr);
+ CFont::SetDropColor(CRGBA(0, 0, 0, 255));
+ CFont::SetDropShadowPosition(3);
+ CFont::SetColor(CRGBA(243, 237, 71, 255));
+ CFont::SetScale(SCREEN_STRETCH_X(1.2f), SCREEN_STRETCH_Y(1.2f));
+ CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_STRETCH_FROM_BOTTOM(110.0f), TheText.Get("WELCOME"));
+ TextCopy(wstr, name);
+ TheText.UpperCase(wstr);
+ CFont::SetColor(CRGBA(243, 237, 71, 255));
+ CFont::SetScale(SCREEN_STRETCH_X(1.2f), SCREEN_STRETCH_Y(1.2f));
+ CFont::PrintString(SCREEN_WIDTH-20, SCREEN_STRETCH_FROM_BOTTOM(80.0f), wstr);
+ CFont::DrawFonts();
+ DoRWStuffEndOfFrame();
+}
+
+char*
+GetLevelSplashScreen(int level)
+{
+ static char *splashScreens[4] = {
+ nil,
+ "splash1",
+ "splash2",
+ "splash3",
+ };
+
+ return splashScreens[level];
+}
+
+char*
+GetRandomSplashScreen(void)
+{
+ int index;
+ static int index2 = 0;
+ static char splashName[128];
+ static int splashIndex[24] = {
+ 25, 22, 4, 13,
+ 1, 21, 14, 16,
+ 10, 12, 5, 9,
+ 11, 18, 3, 2,
+ 19, 23, 7, 17,
+ 15, 6, 8, 20
+ };
+
+ index = splashIndex[4*index2 + CGeneral::GetRandomNumberInRange(0, 3)];
+ index2++;
+ if(index2 == 6)
+ index2 = 0;
+ sprintf(splashName, "loadsc%d", index);
+ return splashName;
+}
+
+#include "rwcore.h"
+#include "rpworld.h"
+#include "rpmatfx.h"
+#include "rpskin.h"
+#include "rphanim.h"
+#include "rtbmp.h"
+
+_TODO("temp, move this includes out of here")
+
+static RwBool
+PluginAttach(void)
+{
+ if( !RpWorldPluginAttach() )
+ {
+ printf("Couldn't attach world plugin\n");
+
+ return FALSE;
+ }
+
+ if( !RpSkinPluginAttach() )
+ {
+ printf("Couldn't attach RpSkin plugin\n");
+
+ return FALSE;
+ }
+
+ if( !RpHAnimPluginAttach() )
+ {
+ printf("Couldn't attach RpHAnim plugin\n");
+
+ return FALSE;
+ }
+
+ if( !NodeNamePluginAttach() )
+ {
+ printf("Couldn't attach node name plugin\n");
+
+ return FALSE;
+ }
+
+ if( !CVisibilityPlugins::PluginAttach() )
+ {
+ printf("Couldn't attach visibility plugins\n");
+
+ return FALSE;
+ }
+
+ if( !RpAnimBlendPluginAttach() )
+ {
+ printf("Couldn't attach RpAnimBlend plugin\n");
+
+ return FALSE;
+ }
+
+ if( !RpMatFXPluginAttach() )
+ {
+ printf("Couldn't attach RpMatFX plugin\n");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static RwBool
+Initialise3D(void *param)
+{
+ if (RsRwInitialise(param))
+ {
+ //
+ DebugMenuInit();
+ DebugMenuPopulate();
+ //
+
+ return CGame::InitialiseRenderWare();
+ }
+
+ return (FALSE);
+}
+
+
+static void
+Terminate3D(void)
+{
+ CGame::ShutdownRenderWare();
+
+ RsRwTerminate();
+
+ return;
+}
+
+RsEventStatus
+AppEventHandler(RsEvent event, void *param)
+{
+ switch( event )
+ {
+ case rsINITIALISE:
+ {
+ CGame::InitialiseOnceBeforeRW();
+ return RsInitialise() ? rsEVENTPROCESSED : rsEVENTERROR;
+ }
+
+ case rsCAMERASIZE:
+ {
+
+ CameraSize(Scene.camera, (RwRect *)param,
+ DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO);
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsRWINITIALISE:
+ {
+ return Initialise3D(param) ? rsEVENTPROCESSED : rsEVENTERROR;
+ }
+
+ case rsRWTERMINATE:
+ {
+ Terminate3D();
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsTERMINATE:
+ {
+ CGame::FinalShutdown();
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsPLUGINATTACH:
+ {
+ return PluginAttach() ? rsEVENTPROCESSED : rsEVENTERROR;
+ }
+
+ case rsINPUTDEVICEATTACH:
+ {
+ AttachInputDevices();
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsIDLE:
+ {
+ Idle(param);
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsFRONTENDIDLE:
+ {
+ FrontendIdle();
+
+ return rsEVENTPROCESSED;
+ }
+
+ case rsACTIVATE:
+ {
+ param ? DMAudio.ReacquireDigitalHandle() : DMAudio.ReleaseDigitalHandle();
+
+ return rsEVENTPROCESSED;
+ }
+
+ default:
+ {
+ return rsEVENTNOTPROCESSED;
+ }
+ }
+}
+
+void PrintGameVersion()
+{
+ CFont::SetPropOn();
+ CFont::SetBackgroundOff();
+ CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(0.5f));
+ CFont::SetCentreOff();
+ CFont::SetRightJustifyOff();
+ CFont::SetBackGroundOnlyTextOff();
+ CFont::SetFontStyle(FONT_BANK);
+ CFont::SetWrapx(SCREEN_WIDTH);
+ CFont::SetDropShadowPosition(0);
+ CFont::SetDropColor(CRGBA(0, 0, 0, 255));
+ CFont::SetColor(CRGBA(235, 170, 50, 255));
+
+ strcpy(gString, "RE3");
+ AsciiToUnicode(gString, gUString);
+ CFont::PrintString(SCREEN_SCALE_X(10.5f), SCREEN_SCALE_Y(8.0f), gUString);
+}
+
+STARTPATCHES
+ InjectHook(0x48E480, Idle, PATCH_JUMP);
+ InjectHook(0x48E700, FrontendIdle, PATCH_JUMP);
+
+ InjectHook(0x48CF10, DoRWStuffStartOfFrame, PATCH_JUMP);
+ InjectHook(0x48D040, DoRWStuffStartOfFrame_Horizon, PATCH_JUMP);
+ InjectHook(0x48E030, RenderScene, PATCH_JUMP);
+ InjectHook(0x48E080, RenderDebugShit, PATCH_JUMP);
+ InjectHook(0x48E090, RenderEffects, PATCH_JUMP);
+ InjectHook(0x48E0E0, Render2dStuff, PATCH_JUMP);
+ InjectHook(0x48E450, RenderMenus, PATCH_JUMP);
+ InjectHook(0x48D120, DoFade, PATCH_JUMP);
+ InjectHook(0x48E470, Render2dStuffAfterFade, PATCH_JUMP);
+
+ InjectHook(0x48D550, LoadSplash, PATCH_JUMP);
+ InjectHook(0x48D670, DestroySplashScreen, PATCH_JUMP);
+ InjectHook(0x48D770, LoadingScreen, PATCH_JUMP);
+ InjectHook(0x48D760, ResetLoadingScreenBar, PATCH_JUMP);
+
+ InjectHook(0x48D470, PluginAttach, PATCH_JUMP);
+ InjectHook(0x48D520, Initialise3D, PATCH_JUMP);
+ InjectHook(0x48D540, Terminate3D, PATCH_JUMP);
+ InjectHook(0x48E800, AppEventHandler, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/core/main.h b/src/core/main.h
new file mode 100644
index 00000000..bdb0e008
--- /dev/null
+++ b/src/core/main.h
@@ -0,0 +1,22 @@
+#pragma once
+
+struct GlobalScene
+{
+ RpWorld *world;
+ RwCamera *camera;
+};
+extern GlobalScene &Scene;
+
+extern uint8 work_buff[55000];
+extern char gString[256];
+extern wchar *gUString;
+extern bool &b_FoundRecentSavedGameWantToLoad;
+
+class CSprite2d;
+
+void InitialiseGame(void);
+void LoadingScreen(const char *str1, const char *str2, const char *splashscreen);
+void LoadingIslandScreen(const char *levelName);
+CSprite2d *LoadSplash(const char *name);
+char *GetLevelSplashScreen(int level);
+char *GetRandomSplashScreen(void);
diff --git a/src/core/patcher.cpp b/src/core/patcher.cpp
new file mode 100644
index 00000000..5fdbdf8b
--- /dev/null
+++ b/src/core/patcher.cpp
@@ -0,0 +1,22 @@
+#include "common.h"
+#include "patcher.h"
+
+StaticPatcher *StaticPatcher::ms_head;
+
+StaticPatcher::StaticPatcher(Patcher func)
+ : m_func(func)
+{
+ m_next = ms_head;
+ ms_head = this;
+}
+
+void
+StaticPatcher::Apply()
+{
+ StaticPatcher *current = ms_head;
+ while(current){
+ current->Run();
+ current = current->m_next;
+ }
+ ms_head = nil;
+}
diff --git a/src/core/patcher.h b/src/core/patcher.h
new file mode 100644
index 00000000..87a6bea4
--- /dev/null
+++ b/src/core/patcher.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#define WRAPPER __declspec(naked)
+#define DEPRECATED __declspec(deprecated)
+#define EAXJMP(a) { _asm mov eax, a _asm jmp eax }
+#define VARJMP(a) { _asm jmp a }
+#define WRAPARG(a) UNREFERENCED_PARAMETER(a)
+
+#define NOVMT __declspec(novtable)
+#define SETVMT(a) *((DWORD_PTR*)this) = (DWORD_PTR)a
+
+#include <algorithm>
+#include <vector>
+
+#include "common.h"
+
+enum
+{
+ PATCH_CALL,
+ PATCH_JUMP,
+ PATCH_NOTHING,
+};
+
+enum
+{
+ III_10 = 1,
+ III_11,
+ III_STEAM,
+ VC_10,
+ VC_11,
+ VC_STEAM
+};
+
+extern int gtaversion;
+
+class StaticPatcher
+{
+private:
+ using Patcher = void(*)();
+
+ Patcher m_func;
+ StaticPatcher *m_next;
+ static StaticPatcher *ms_head;
+
+ void Run() { m_func(); }
+public:
+ StaticPatcher(Patcher func);
+ static void Apply();
+};
+
+template<typename T>
+inline T AddressByVersion(uint32_t addressIII10, uint32_t addressIII11, uint32_t addressIIISteam, uint32_t addressvc10, uint32_t addressvc11, uint32_t addressvcSteam)
+{
+ if(gtaversion == -1){
+ if(*(uint32_t*)0x5C1E75 == 0xB85548EC) gtaversion = III_10;
+ else if(*(uint32_t*)0x5C2135 == 0xB85548EC) gtaversion = III_11;
+ else if(*(uint32_t*)0x5C6FD5 == 0xB85548EC) gtaversion = III_STEAM;
+ else if(*(uint32_t*)0x667BF5 == 0xB85548EC) gtaversion = VC_10;
+ else if(*(uint32_t*)0x667C45 == 0xB85548EC) gtaversion = VC_11;
+ else if(*(uint32_t*)0x666BA5 == 0xB85548EC) gtaversion = VC_STEAM;
+ else gtaversion = 0;
+ }
+ switch(gtaversion){
+ case III_10:
+ return (T)addressIII10;
+ case III_11:
+ return (T)addressIII11;
+ case III_STEAM:
+ return (T)addressIIISteam;
+ case VC_10:
+ return (T)addressvc10;
+ case VC_11:
+ return (T)addressvc11;
+ case VC_STEAM:
+ return (T)addressvcSteam;
+ default:
+ return (T)0;
+ }
+}
+
+inline bool
+is10(void)
+{
+ return gtaversion == III_10 || gtaversion == VC_10;
+}
+
+inline bool
+isIII(void)
+{
+ return gtaversion >= III_10 && gtaversion <= III_STEAM;
+}
+
+inline bool
+isVC(void)
+{
+ return gtaversion >= VC_10 && gtaversion <= VC_STEAM;
+}
+
+#define PTRFROMCALL(addr) (uint32_t)(*(uint32_t*)((uint32_t)addr+1) + (uint32_t)addr + 5)
+#define INTERCEPT(saved, func, a) \
+{ \
+ saved = PTRFROMCALL(a); \
+ InjectHook(a, func); \
+}
+
+template<typename T, typename AT> inline void
+Patch(AT address, T value)
+{
+ DWORD dwProtect[2];
+ VirtualProtect((void*)address, sizeof(T), PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ *(T*)address = value;
+ VirtualProtect((void*)address, sizeof(T), dwProtect[0], &dwProtect[1]);
+}
+
+template<typename AT> inline void
+Nop(AT address, unsigned int nCount)
+{
+ DWORD dwProtect[2];
+ VirtualProtect((void*)address, nCount, PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ memset((void*)address, 0x90, nCount);
+ VirtualProtect((void*)address, nCount, dwProtect[0], &dwProtect[1]);
+}
+
+template<typename AT> inline void
+ClearCC(AT address, unsigned int nCount)
+{
+ DWORD dwProtect[2];
+ VirtualProtect((void*)address, nCount, PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ memset((void*)address, 0xCC, nCount);
+ VirtualProtect((void*)address, nCount, dwProtect[0], &dwProtect[1]);
+}
+
+extern std::vector<int32> usedAddresses;
+
+template<typename AT, typename HT> inline void
+InjectHook(AT address, HT hook, unsigned int nType=PATCH_NOTHING)
+{
+ if(std::any_of(usedAddresses.begin(), usedAddresses.end(),
+ [address](AT value) { return (int32)value == address; })) {
+ debug("Used address %#06x twice when injecting hook\n", address);
+ }
+
+ usedAddresses.push_back((int32)address);
+
+ DWORD dwProtect[2];
+ switch ( nType )
+ {
+ case PATCH_JUMP:
+ VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ *(BYTE*)address = 0xE9;
+ break;
+ case PATCH_CALL:
+ VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ *(BYTE*)address = 0xE8;
+ break;
+ default:
+ VirtualProtect((void*)((DWORD)address + 1), 4, PAGE_EXECUTE_READWRITE, &dwProtect[0]);
+ break;
+ }
+ DWORD dwHook;
+ _asm
+ {
+ mov eax, hook
+ mov dwHook, eax
+ }
+
+ *(ptrdiff_t*)((DWORD)address + 1) = (DWORD)dwHook - (DWORD)address - 5;
+ if ( nType == PATCH_NOTHING )
+ VirtualProtect((void*)((DWORD)address + 1), 4, dwProtect[0], &dwProtect[1]);
+ else
+ VirtualProtect((void*)address, 5, dwProtect[0], &dwProtect[1]);
+}
+
+inline void ExtractCall(void *dst, uint32_t a)
+{
+ *(uint32_t*)dst = (uint32_t)(*(uint32_t*)(a+1) + a + 5);
+}
+template<typename T>
+inline void InterceptCall(void *dst, T func, uint32_t a)
+{
+ ExtractCall(dst, a);
+ InjectHook(a, func);
+}
+template<typename T>
+inline void InterceptVmethod(void *dst, T func, uint32_t a)
+{
+ *(uint32_t*)dst = *(uint32_t*)a;
+ Patch(a, func);
+}
+
+#define STARTPATCHES static StaticPatcher Patcher([](){
+#define ENDPATCHES });
diff --git a/src/core/re3.cpp b/src/core/re3.cpp
new file mode 100644
index 00000000..9dc39d46
--- /dev/null
+++ b/src/core/re3.cpp
@@ -0,0 +1,381 @@
+#include <direct.h>
+#include <csignal>
+#include <windows.h>
+#include "common.h"
+#include "patcher.h"
+#include "Renderer.h"
+#include "Credits.h"
+#include "Camera.h"
+#include "Weather.h"
+#include "Clock.h"
+#include "World.h"
+#include "Vehicle.h"
+#include "Streaming.h"
+#include "PathFind.h"
+#include "Boat.h"
+#include "Automobile.h"
+#include "debugmenu_public.h"
+
+#include <vector>
+
+std::vector<int32> usedAddresses;
+
+void **rwengine = *(void***)0x5A10E1;
+
+DebugMenuAPI gDebugMenuAPI;
+
+WRAPPER void *gtanew(uint32 sz) { EAXJMP(0x5A0690); }
+WRAPPER void gtadelete(void *p) { EAXJMP(0x5A07E0); }
+
+// overload our own new/delete with GTA's functions
+void *operator new(size_t sz) { return gtanew(sz); }
+void operator delete(void *ptr) noexcept { gtadelete(ptr); }
+
+#ifdef USE_PS2_RAND
+unsigned __int64 myrand_seed = 1;
+#else
+unsigned long int myrand_seed = 1;
+#endif
+
+int
+myrand(void)
+{
+#ifdef USE_PS2_RAND
+ // Use our own implementation of rand, stolen from PS2
+ myrand_seed = 0x5851F42D4C957F2D * myrand_seed + 1;
+ return ((myrand_seed >> 32) & 0x7FFFFFFF);
+#else
+ // or original codewarrior rand
+ myrand_seed = myrand_seed * 1103515245 + 12345;
+ return((myrand_seed >> 16) & 0x7FFF);
+#endif
+}
+
+void
+mysrand(unsigned int seed)
+{
+ myrand_seed = seed;
+}
+
+int (*open_script_orig)(const char *path, const char *mode);
+int
+open_script(const char *path, const char *mode)
+{
+ static int scriptToLoad = 1;
+
+ if(GetAsyncKeyState('G') & 0x8000)
+ scriptToLoad = 0;
+ if(GetAsyncKeyState('R') & 0x8000)
+ scriptToLoad = 1;
+ if(GetAsyncKeyState('D') & 0x8000)
+ scriptToLoad = 2;
+
+ switch(scriptToLoad){
+ case 0: return open_script_orig(path, mode);
+ case 1: return open_script_orig("main_freeroam.scm", mode);
+ case 2: return open_script_orig("main_d.scm", mode);
+ }
+ return open_script_orig(path, mode);
+}
+
+int gDbgSurf;
+
+void (*DebugMenuProcess)(void);
+void (*DebugMenuRender)(void);
+static void stub(void) { }
+
+void
+DebugMenuInit(void)
+{
+ if(DebugMenuLoad()){
+ DebugMenuProcess = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuProcess");
+ DebugMenuRender = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuRender");
+ }
+ if(DebugMenuProcess == nil || DebugMenuRender == nil){
+ DebugMenuProcess = stub;
+ DebugMenuRender = stub;
+ }
+
+}
+
+void WeaponCheat();
+void HealthCheat();
+void TankCheat();
+void BlowUpCarsCheat();
+void ChangePlayerCheat();
+void MayhemCheat();
+void EverybodyAttacksPlayerCheat();
+void WeaponsForAllCheat();
+void FastTimeCheat();
+void SlowTimeCheat();
+void MoneyCheat();
+void ArmourCheat();
+void WantedLevelUpCheat();
+void WantedLevelDownCheat();
+void SunnyWeatherCheat();
+void CloudyWeatherCheat();
+void RainyWeatherCheat();
+void FoggyWeatherCheat();
+void FastWeatherCheat();
+void OnlyRenderWheelsCheat();
+void ChittyChittyBangBangCheat();
+void StrongGripCheat();
+void NastyLimbsCheat();
+
+// needs too much stuff for now
+#if 0
+void
+spawnCar(int id)
+{
+ CVector playerpos;
+ CStreaming::RequestModel(id, 0);
+ CStreaming::LoadAllRequestedModels(false);
+ if(CStreaming::HasModelLoaded(id)){
+ FindPlayerCoors(playerpos);
+ int node = ThePaths.FindNodeClosestToCoors(playerpos, 0, 100.0f, false, false);
+ if(node < 0)
+ return;
+
+ CVehicle *v;
+ if(CModelInfo::IsBoatModel(id)){
+// CBoat* boat = (CBoat*)CVehicle__new(0x484);
+// boat = boat->ctor(id, 1);
+// v = (CVehicle*)(boat);
+ }else{
+// CAutomobile *au = (CAutomobile*)CVehicle__new(0x5A8);
+// au = au->ctor(id, 1);
+// v = (CVehicle*)au;
+ }
+/*
+ // unlock doors
+ FIELD(int, v, 0x224) = 1;
+ // set player owned
+ FIELD(uint8, v, 0x1F7) |= 4;
+
+ DebugMenuEntrySetAddress(carCol1, &FIELD(uchar, v, 0x19C));
+ DebugMenuEntrySetAddress(carCol2, &FIELD(uchar, v, 0x19D));
+ //if(id == MODELID_ESPERANTO)
+ // FIELD(uchar, v, 0x19C) = 54;
+
+ v->matrix.matrix.pos.x = ThePaths.nodes[node].x;
+ v->matrix.matrix.pos.y = ThePaths.nodes[node].y;
+ v->matrix.matrix.pos.z = ThePaths.nodes[node].z + 4.0f;
+ float x = v->matrix.matrix.pos.x;
+ float y = v->matrix.matrix.pos.y;
+ float z = v->matrix.matrix.pos.z;
+ v->matrix.SetRotate(0.0f, 0.0f, 3.49f);
+ v->matrix.matrix.pos.x += x;
+ v->matrix.matrix.pos.y += y;
+ v->matrix.matrix.pos.z += z;
+ v->bfTypeStatus = v->bfTypeStatus & 7 | 0x20;
+ FIELD(int, v, 0x224) = 1;
+*/
+ CWorld::Add(v);
+ }
+}
+#endif
+
+void
+DebugMenuPopulate(void)
+{
+ if(DebugMenuLoad()){
+ static const char *weathers[] = {
+ "Sunny", "Cloudy", "Rainy", "Foggy"
+ };
+ DebugMenuEntry *e;
+ e = DebugMenuAddVar("Time & Weather", "Current Hour", &CClock::GetHoursRef(), nil, 1, 0, 23, nil);
+ DebugMenuEntrySetWrap(e, true);
+ e = DebugMenuAddVar("Time & Weather", "Current Minute", &CClock::GetMinutesRef(),
+ [](){ CWeather::InterpolationValue = CClock::GetMinutes()/60.0f; }, 1, 0, 59, nil);
+ DebugMenuEntrySetWrap(e, true);
+ e = DebugMenuAddVar("Time & Weather", "Old Weather", (int16*)&CWeather::OldWeatherType, nil, 1, 0, 3, weathers);
+ DebugMenuEntrySetWrap(e, true);
+ e = DebugMenuAddVar("Time & Weather", "New Weather", (int16*)&CWeather::NewWeatherType, nil, 1, 0, 3, weathers);
+ DebugMenuEntrySetWrap(e, true);
+ DebugMenuAddVar("Time & Weather", "Wind", (float*)&CWeather::Wind, nil, 0.1f, 0.0f, 1.0f);
+ DebugMenuAddVar("Time & Weather", "Time scale", (float*)0x8F2C20, nil, 0.1f, 0.0f, 10.0f);
+
+ DebugMenuAddCmd("Cheats", "Weapons", WeaponCheat);
+ DebugMenuAddCmd("Cheats", "Money", MoneyCheat);
+ DebugMenuAddCmd("Cheats", "Health", HealthCheat);
+ DebugMenuAddCmd("Cheats", "Wanted level up", WantedLevelUpCheat);
+ DebugMenuAddCmd("Cheats", "Wanted level down", WantedLevelDownCheat);
+ DebugMenuAddCmd("Cheats", "Tank", TankCheat);
+ DebugMenuAddCmd("Cheats", "Blow up cars", BlowUpCarsCheat);
+ DebugMenuAddCmd("Cheats", "Change player", ChangePlayerCheat);
+ DebugMenuAddCmd("Cheats", "Mayhem", MayhemCheat);
+ DebugMenuAddCmd("Cheats", "Everybody attacks player", EverybodyAttacksPlayerCheat);
+ DebugMenuAddCmd("Cheats", "Weapons for all", WeaponsForAllCheat);
+ DebugMenuAddCmd("Cheats", "Fast time", FastTimeCheat);
+ DebugMenuAddCmd("Cheats", "Slow time", SlowTimeCheat);
+ DebugMenuAddCmd("Cheats", "Armour", ArmourCheat);
+ DebugMenuAddCmd("Cheats", "Sunny weather", SunnyWeatherCheat);
+ DebugMenuAddCmd("Cheats", "Cloudy weather", CloudyWeatherCheat);
+ DebugMenuAddCmd("Cheats", "Rainy weather", RainyWeatherCheat);
+ DebugMenuAddCmd("Cheats", "Foggy weather", FoggyWeatherCheat);
+ DebugMenuAddCmd("Cheats", "Fast weather", FastWeatherCheat);
+ DebugMenuAddCmd("Cheats", "Only render wheels", OnlyRenderWheelsCheat);
+ DebugMenuAddCmd("Cheats", "Chitty chitty bang bang", ChittyChittyBangBangCheat);
+ DebugMenuAddCmd("Cheats", "Strong grip", StrongGripCheat);
+ DebugMenuAddCmd("Cheats", "Nasty limbs", NastyLimbsCheat);
+
+ DebugMenuAddVarBool8("Debug", "Show Ped Road Groups", (int8*)&gbShowPedRoadGroups, nil);
+ DebugMenuAddVarBool8("Debug", "Show Car Road Groups", (int8*)&gbShowCarRoadGroups, nil);
+ DebugMenuAddVarBool8("Debug", "Show Collision Polys", (int8*)&gbShowCollisionPolys, nil);
+ DebugMenuAddVarBool8("Debug", "Don't render Buildings", (int8*)&gbDontRenderBuildings, nil);
+ DebugMenuAddVarBool8("Debug", "Don't render Big Buildings", (int8*)&gbDontRenderBigBuildings, nil);
+ DebugMenuAddVarBool8("Debug", "Don't render Peds", (int8*)&gbDontRenderPeds, nil);
+ DebugMenuAddVarBool8("Debug", "Don't render Objects", (int8*)&gbDontRenderObjects, nil);
+ DebugMenuAddVar("Debug", "Dbg Surface", &gDbgSurf, nil, 1, 0, 34, nil);
+
+ DebugMenuAddCmd("Debug", "Start Credits", CCredits::Start);
+ DebugMenuAddCmd("Debug", "Stop Credits", CCredits::Stop);
+ }
+}
+
+/*
+int (*RsEventHandler_orig)(int a, int b);
+int
+delayedPatches10(int a, int b)
+{
+ DebugMenuInit();
+ DebugMenuPopulate();
+
+ return RsEventHandler_orig(a, b);
+}
+*/
+
+void __declspec(naked) HeadlightsFix()
+{
+ static const float fMinusOne = -1.0f;
+ _asm
+ {
+ fld [esp+708h-690h]
+ fcomp fMinusOne
+ fnstsw ax
+ and ah, 5
+ cmp ah, 1
+ jnz HeadlightsFix_DontLimit
+ fld fMinusOne
+ fstp [esp+708h-690h]
+
+HeadlightsFix_DontLimit:
+ fld [esp+708h-690h]
+ fabs
+ fld st
+ push 0x5382F2
+ retn
+ }
+}
+
+const int re3_buffsize = 1024;
+static char re3_buff[re3_buffsize];
+
+void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func)
+{
+ int nCode;
+
+ strcpy_s(re3_buff, re3_buffsize, "Assertion failed!" );
+ strcat_s(re3_buff, re3_buffsize, "\n" );
+
+ strcat_s(re3_buff, re3_buffsize, "File: ");
+ strcat_s(re3_buff, re3_buffsize, filename );
+ strcat_s(re3_buff, re3_buffsize, "\n" );
+
+ strcat_s(re3_buff, re3_buffsize, "Line: " );
+ _itoa_s( lineno, re3_buff + strlen(re3_buff), re3_buffsize - strlen(re3_buff), 10 );
+ strcat_s(re3_buff, re3_buffsize, "\n");
+
+ strcat_s(re3_buff, re3_buffsize, "Function: ");
+ strcat_s(re3_buff, re3_buffsize, func );
+ strcat_s(re3_buff, re3_buffsize, "\n" );
+
+ strcat_s(re3_buff, re3_buffsize, "Expression: ");
+ strcat_s(re3_buff, re3_buffsize, expr);
+ strcat_s(re3_buff, re3_buffsize, "\n");
+
+ strcat_s(re3_buff, re3_buffsize, "\n" );
+ strcat_s(re3_buff, re3_buffsize, "(Press Retry to debug the application)");
+
+
+ nCode = ::MessageBoxA(nil, re3_buff, "RE3 Assertion Failed!",
+ MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
+
+ if (nCode == IDABORT)
+ {
+ raise(SIGABRT);
+ _exit(3);
+ }
+
+ if (nCode == IDRETRY)
+ {
+ __debugbreak();
+ return;
+ }
+
+ if (nCode == IDIGNORE)
+ return;
+
+ abort();
+}
+
+void re3_debug(char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ vsprintf_s(re3_buff, re3_buffsize, format, va);
+ va_end(va);
+
+ printf("%s", re3_buff);
+}
+
+void re3_trace(const char *filename, unsigned int lineno, const char *func, char *format, ...)
+{
+ char buff[re3_buffsize *2];
+ va_list va;
+ va_start(va, format);
+ vsprintf_s(re3_buff, re3_buffsize, format, va);
+ va_end(va);
+
+ sprintf_s(buff, re3_buffsize * 2, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff);
+
+ OutputDebugStringA(buff);
+}
+
+void
+patch()
+{
+ StaticPatcher::Apply();
+
+// Patch<float>(0x46BC61+6, 1.0f); // car distance
+ InjectHook(0x59E460, printf, PATCH_JUMP);
+ InjectHook(0x475E00, printf, PATCH_JUMP); // _Error
+
+
+ // stolen from silentpatch (sorry)
+ Patch<WORD>(0x5382BF, 0x0EEB);
+ InjectHook(0x5382EC, HeadlightsFix, PATCH_JUMP);
+
+ InterceptCall(&open_script_orig, open_script, 0x438869);
+
+// InterceptCall(&RsEventHandler_orig, delayedPatches10, 0x58275E);
+}
+
+BOOL WINAPI
+DllMain(HINSTANCE hInst, DWORD reason, LPVOID)
+{
+ if(reason == DLL_PROCESS_ATTACH){
+
+ AllocConsole();
+ freopen("CONIN$", "r", stdin);
+ freopen("CONOUT$", "w", stdout);
+ freopen("CONOUT$", "w", stderr);
+
+ if (*(DWORD*)0x5C1E75 == 0xB85548EC) // 1.0
+ patch();
+ else
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/core/rw.cpp b/src/core/rw.cpp
new file mode 100644
index 00000000..52bcf5bb
--- /dev/null
+++ b/src/core/rw.cpp
@@ -0,0 +1,415 @@
+#include "common.h"
+#include "patcher.h"
+#include "rwcore.h"
+#include "rpworld.h"
+#include "rpmatfx.h"
+#include "rpskin.h"
+#include "rphanim.h"
+#include "rtbmp.h"
+
+typedef RwV3d *(*rwVectorsMultFn) (RwV3d * pointsOut,
+ const RwV3d * pointsIn,
+ RwInt32 numPoints,
+ const RwMatrix * matrix);
+
+
+WRAPPER void _rwObjectHasFrameSetFrame(void* object, RwFrame* frame) { EAXJMP(0x5BC950); }
+WRAPPER RpAtomic* AtomicDefaultRenderCallBack(RpAtomic* atomic) { EAXJMP(0x59E690); }
+WRAPPER void _rpAtomicResyncInterpolatedSphere(RpAtomic* atomic) { EAXJMP(0x59E6C0); }
+WRAPPER RwSphere const* RpAtomicGetWorldBoundingSphere(RpAtomic* atomic) { EAXJMP(0x59E800); }
+WRAPPER RwInt32 RpClumpGetNumAtomics(RpClump* clump) { EAXJMP(0x59ED50); }
+WRAPPER RpClump* RpClumpRender(RpClump* clump) { EAXJMP(0x59ED80); }
+WRAPPER RpClump* RpClumpForAllAtomics(RpClump* clump, RpAtomicCallBack callback, void* pData) { EAXJMP(0x59EDD0); }
+WRAPPER RpClump* RpClumpForAllCameras(RpClump* clump, RwCameraCallBack callback, void* pData) { EAXJMP(0x59EE10); }
+WRAPPER RpClump* RpClumpForAllLights(RpClump* clump, RpLightCallBack callback, void* pData) { EAXJMP(0x59EE60); }
+WRAPPER RpAtomic* RpAtomicCreate() { EAXJMP(0x59EEB0); }
+WRAPPER RpAtomic* RpAtomicSetGeometry(RpAtomic* atomic, RpGeometry* geometry, RwUInt32 flags) { EAXJMP(0x59EFA0); }
+WRAPPER RwBool RpAtomicDestroy(RpAtomic* atomic) { EAXJMP(0x59F020); }
+WRAPPER RpAtomic* RpAtomicClone(RpAtomic* atomic) { EAXJMP(0x59F0A0); }
+WRAPPER RpClump* RpClumpClone(RpClump* clump) { EAXJMP(0x59F1B0); }
+WRAPPER RpClump* RpClumpCreate() { EAXJMP(0x59F490); }
+WRAPPER RwBool RpClumpDestroy(RpClump* clump) { EAXJMP(0x59F500); }
+WRAPPER RpClump* RpClumpAddAtomic(RpClump* clump, RpAtomic* atomic) { EAXJMP(0x59F680); }
+WRAPPER RpClump* RpClumpRemoveAtomic(RpClump* clump, RpAtomic* atomic) { EAXJMP(0x59F6B0); }
+WRAPPER RpClump* RpClumpRemoveLight(RpClump* clump, RpLight* light) { EAXJMP(0x59F6E0); }
+WRAPPER RpClump* RpClumpStreamRead(RwStream* stream) { EAXJMP(0x59FC50); }
+WRAPPER RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A0510); }
+WRAPPER RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A0540); }
+WRAPPER RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5A0570); }
+WRAPPER RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB) { EAXJMP(0x5A05A0); }
+WRAPPER RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB) { EAXJMP(0x5A05C0); }
+WRAPPER RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID) { EAXJMP(0x5A05E0); }
+WRAPPER RpAtomic* RpAtomicSetFrame(RpAtomic* atomic, RwFrame* frame) { EAXJMP(0x5A0600); }
+WRAPPER RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB) { EAXJMP(0x5A0DC0); }
+WRAPPER RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID) { EAXJMP(0x5A0DF0); }
+WRAPPER RwInt32 RwEngineGetNumSubSystems() { EAXJMP(0x5A0E10); }
+WRAPPER RwSubSystemInfo* RwEngineGetSubSystemInfo(RwSubSystemInfo* subSystemInfo, RwInt32 subSystemIndex) { EAXJMP(0x5A0E40); }
+WRAPPER RwInt32 RwEngineGetCurrentSubSystem() { EAXJMP(0x5A0E70); }
+WRAPPER RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex) { EAXJMP(0x5A0EA0); }
+WRAPPER RwInt32 RwEngineGetNumVideoModes() { EAXJMP(0x5A0ED0); }
+WRAPPER RwVideoMode* RwEngineGetVideoModeInfo(RwVideoMode* modeinfo, RwInt32 modeIndex) { EAXJMP(0x5A0F00); }
+WRAPPER RwInt32 RwEngineGetCurrentVideoMode() { EAXJMP(0x5A0F30); }
+WRAPPER RwBool RwEngineSetVideoMode(RwInt32 modeIndex) { EAXJMP(0x5A0F60); }
+WRAPPER RwBool RwEngineStop() { EAXJMP(0x5A0F90); }
+WRAPPER RwBool RwEngineStart() { EAXJMP(0x5A0FE0); }
+WRAPPER RwBool RwEngineClose() { EAXJMP(0x5A1070); }
+WRAPPER RwBool RwEngineOpen(RwEngineOpenParams* initParams) { EAXJMP(0x5A10E0); }
+WRAPPER RwBool RwEngineTerm() { EAXJMP(0x5A1290); }
+WRAPPER RwBool RwEngineInit(RwMemoryFunctions* memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize) { EAXJMP(0x5A12D0); }
+WRAPPER void* _rwFrameOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A15E0); }
+WRAPPER void* _rwFrameClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A1650); }
+WRAPPER RwFrame* _rwFrameCloneAndLinkClones(RwFrame* root) { EAXJMP(0x5A1690); }
+WRAPPER RwFrame* _rwFramePurgeClone(RwFrame* root) { EAXJMP(0x5A1880); }
+WRAPPER RwBool RwFrameDirty(RwFrame const* frame) { EAXJMP(0x5A1930); }
+WRAPPER void _rwFrameInit(RwFrame* frame) { EAXJMP(0x5A1950); }
+WRAPPER RwFrame* RwFrameCreate() { EAXJMP(0x5A1A00); }
+WRAPPER RwBool RwFrameDestroy(RwFrame* frame) { EAXJMP(0x5A1A30); }
+WRAPPER RwBool RwFrameDestroyHierarchy(RwFrame* frame) { EAXJMP(0x5A1BF0); }
+WRAPPER RwFrame* RwFrameUpdateObjects(RwFrame* frame) { EAXJMP(0x5A1C60); }
+WRAPPER RwMatrix* RwFrameGetLTM(RwFrame* frame) { EAXJMP(0x5A1CE0); }
+WRAPPER RwFrame* RwFrameAddChild(RwFrame* parent, RwFrame* child) { EAXJMP(0x5A1D00); }
+WRAPPER RwFrame* RwFrameRemoveChild(RwFrame* child) { EAXJMP(0x5A1ED0); }
+WRAPPER RwFrame* RwFrameForAllChildren(RwFrame* frame, RwFrameCallBack callBack, void* data) { EAXJMP(0x5A1FC0); }
+WRAPPER RwFrame* RwFrameTranslate(RwFrame* frame, RwV3d const* v, RwOpCombineType combine) { EAXJMP(0x5A2000); }
+WRAPPER RwFrame* RwFrameScale(RwFrame* frame, RwV3d const* v, RwOpCombineType combine) { EAXJMP(0x5A20A0); }
+WRAPPER RwFrame* RwFrameTransform(RwFrame* frame, RwMatrix const* m, RwOpCombineType combine) { EAXJMP(0x5A2140); }
+WRAPPER RwFrame* RwFrameRotate(RwFrame* frame, RwV3d const* axis, RwReal angle, RwOpCombineType combine) { EAXJMP(0x5A21E0); }
+WRAPPER RwFrame* RwFrameSetIdentity(RwFrame* frame) { EAXJMP(0x5A2280); }
+WRAPPER RwFrame* RwFrameForAllObjects(RwFrame* frame, RwObjectCallBack callBack, void* data) { EAXJMP(0x5A2340); }
+WRAPPER RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A2380); }
+WRAPPER RwBool _rwMatrixSetMultFn(rwMatrixMultFn multMat) { EAXJMP(0x5A23B0); }
+WRAPPER RwReal _rwMatrixDeterminant(RwMatrix const* matrix) { EAXJMP(0x5A2520); }
+WRAPPER RwReal _rwMatrixOrthogonalError(RwMatrix const* matrix) { EAXJMP(0x5A2570); }
+WRAPPER RwReal _rwMatrixNormalError(RwMatrix const* matrix) { EAXJMP(0x5A25D0); }
+WRAPPER RwReal _rwMatrixIdentityError(RwMatrix const* matrix) { EAXJMP(0x5A2660); }
+WRAPPER void* _rwMatrixClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A2730); }
+WRAPPER void* _rwMatrixOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A2770); }
+WRAPPER RwMatrix* RwMatrixOptimize(RwMatrix* matrix, RwMatrixTolerance const* tolerance) { EAXJMP(0x5A2820); }
+WRAPPER RwMatrix* RwMatrixUpdate(RwMatrix* matrix) { EAXJMP(0x5A28E0); }
+WRAPPER RwMatrix* RwMatrixMultiply(RwMatrix* matrixOut, RwMatrix const* MatrixIn1, RwMatrix const* matrixIn2) { EAXJMP(0x5A28F0); }
+WRAPPER RwMatrix* RwMatrixRotateOneMinusCosineSine(RwMatrix* matrix, RwV3d const* unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp) { EAXJMP(0x5A2960); }
+WRAPPER RwMatrix* RwMatrixRotate(RwMatrix* matrix, RwV3d const* axis, RwReal angle, RwOpCombineType combineOp) { EAXJMP(0x5A2BF0); }
+WRAPPER RwMatrix* RwMatrixInvert(RwMatrix* matrixOut, RwMatrix const* matrixIn) { EAXJMP(0x5A2C90); }
+WRAPPER RwMatrix* RwMatrixScale(RwMatrix* matrix, RwV3d const* scale, RwOpCombineType combineOp) { EAXJMP(0x5A2EE0); }
+WRAPPER RwMatrix* RwMatrixTranslate(RwMatrix* matrix, RwV3d const* translation, RwOpCombineType combineOp) { EAXJMP(0x5A3070); }
+WRAPPER RwMatrix* RwMatrixTransform(RwMatrix* matrix, RwMatrix const* transform, RwOpCombineType combineOp) { EAXJMP(0x5A31C0); }
+WRAPPER RwBool RwMatrixDestroy(RwMatrix* mpMat) { EAXJMP(0x5A3300); }
+WRAPPER RwMatrix* RwMatrixCreate() { EAXJMP(0x5A3330); }
+WRAPPER RwBool _rwVectorSetMultFn(rwVectorMultFn multPoint, rwVectorsMultFn multPoints, rwVectorMultFn multVector, rwVectorsMultFn multVectors) { EAXJMP(0x5A3450); }
+WRAPPER RwReal _rwV3dNormalize(RwV3d* out, RwV3d const* in) { EAXJMP(0x5A3600); }
+WRAPPER RwReal RwV3dLength(RwV3d const* in) { EAXJMP(0x5A36A0); }
+WRAPPER RwReal _rwSqrt(RwReal const num) { EAXJMP(0x5A3710); }
+WRAPPER RwReal _rwInvSqrt(RwReal const num) { EAXJMP(0x5A3770); }
+WRAPPER RwV3d* RwV3dTransformPoints(RwV3d* pointsOut, RwV3d const* pointsIn, RwInt32 numPoints, RwMatrix const* matrix) { EAXJMP(0x5A37D0); }
+WRAPPER RwV3d* RwV3dTransformVectors(RwV3d* vectorsOut, RwV3d const* vectorsIn, RwInt32 numPoints, RwMatrix const* matrix) { EAXJMP(0x5A37E0); }
+WRAPPER void* _rwVectorClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A37F0); }
+WRAPPER void* _rwVectorOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A3860); }
+WRAPPER RwUInt32 RwStreamRead(RwStream* stream, void* buffer, RwUInt32 length) { EAXJMP(0x5A3AD0); }
+WRAPPER RwStream* RwStreamWrite(RwStream* stream, void const* buffer, RwUInt32 length) { EAXJMP(0x5A3C30); }
+WRAPPER RwStream* RwStreamSkip(RwStream* stream, RwUInt32 offset) { EAXJMP(0x5A3DF0); }
+WRAPPER RwBool RwStreamClose(RwStream* stream, void* pData) { EAXJMP(0x5A3F10); }
+WRAPPER RwStream* RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, void const* pData) { EAXJMP(0x5A3FE0); }
+WRAPPER RwReal RwIm2DGetNearScreenZ() { EAXJMP(0x5A43A0); }
+WRAPPER RwReal RwIm2DGetFarScreenZ() { EAXJMP(0x5A43B0); }
+WRAPPER RwBool RwRenderStateSet(RwRenderState state, void* value) { EAXJMP(0x5A43C0); }
+WRAPPER RwBool RwRenderStateGet(RwRenderState state, void* value) { EAXJMP(0x5A4410); }
+WRAPPER RwBool RwIm2DRenderLine(RwIm2DVertex* vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2) { EAXJMP(0x5A4420); }
+WRAPPER RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex* vertices, RwInt32 numVertices) { EAXJMP(0x5A4430); }
+WRAPPER RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex* vertices, RwInt32 numVertices, RwImVertexIndex* indices, RwInt32 numIndices) { EAXJMP(0x5A4440); }
+WRAPPER RwCamera* RwCameraEndUpdate(RwCamera* camera) { EAXJMP(0x5A5020); }
+WRAPPER RwCamera* RwCameraBeginUpdate(RwCamera* camera) { EAXJMP(0x5A5030); }
+WRAPPER RwCamera* RwCameraSetViewOffset(RwCamera* camera, RwV2d const* offset) { EAXJMP(0x5A5040); }
+WRAPPER RwCamera* RwCameraSetNearClipPlane(RwCamera* camera, RwReal nearClip) { EAXJMP(0x5A5070); }
+WRAPPER RwCamera* RwCameraSetFarClipPlane(RwCamera* camera, RwReal farClip) { EAXJMP(0x5A5140); }
+WRAPPER RwFrustumTestResult RwCameraFrustumTestSphere(RwCamera const* camera, RwSphere const* sphere) { EAXJMP(0x5A5170); }
+WRAPPER RwCamera* RwCameraClear(RwCamera* camera, RwRGBA* colour, RwInt32 clearMode) { EAXJMP(0x5A51E0); }
+WRAPPER RwCamera* RwCameraShowRaster(RwCamera* camera, void* pDev, RwUInt32 flags) { EAXJMP(0x5A5210); }
+WRAPPER RwCamera* RwCameraSetProjection(RwCamera* camera, RwCameraProjection projection) { EAXJMP(0x5A5240); }
+WRAPPER RwCamera* RwCameraSetViewWindow(RwCamera* camera, RwV2d const* viewWindow) { EAXJMP(0x5A52B0); }
+WRAPPER RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A52F0); }
+WRAPPER RwBool RwCameraDestroy(RwCamera* camera) { EAXJMP(0x5A5320); }
+WRAPPER RwCamera* RwCameraCreate() { EAXJMP(0x5A5360); }
+WRAPPER RwBool RwTextureSetMipmapping(RwBool enable) { EAXJMP(0x5A7100); }
+WRAPPER RwBool RwTextureGetMipmapping() { EAXJMP(0x5A7120); }
+WRAPPER RwBool RwTextureSetAutoMipmapping(RwBool enable) { EAXJMP(0x5A7130); }
+WRAPPER RwBool RwTextureGetAutoMipmapping() { EAXJMP(0x5A7150); }
+WRAPPER RwTexDictionary* RwTexDictionaryCreate() { EAXJMP(0x5A7160); }
+WRAPPER RwBool RwTexDictionaryDestroy(RwTexDictionary* dict) { EAXJMP(0x5A7200); }
+WRAPPER RwTexDictionary const* RwTexDictionaryForAllTextures(RwTexDictionary const* dict, RwTextureCallBack fpCallBack, void* pData) { EAXJMP(0x5A7290); }
+WRAPPER RwTexture* RwTextureCreate(RwRaster* raster) { EAXJMP(0x5A72D0); }
+WRAPPER RwBool RwTextureDestroy(RwTexture* texture) { EAXJMP(0x5A7330); }
+WRAPPER RwTexture* RwTextureSetName(RwTexture* texture, RwChar const* name) { EAXJMP(0x5A73B0); }
+WRAPPER RwTexture* RwTextureSetMaskName(RwTexture* texture, RwChar const* maskName) { EAXJMP(0x5A7420); }
+WRAPPER RwTexture* RwTexDictionaryAddTexture(RwTexDictionary* dict, RwTexture* texture) { EAXJMP(0x5A7490); }
+WRAPPER RwTexture* RwTexDictionaryFindNamedTexture(RwTexDictionary* dict, RwChar const* name) { EAXJMP(0x5A74D0); }
+WRAPPER RwTexDictionary* RwTexDictionarySetCurrent(RwTexDictionary* dict) { EAXJMP(0x5A7550); }
+WRAPPER RwTexDictionary* RwTexDictionaryGetCurrent() { EAXJMP(0x5A7570); }
+WRAPPER RwTexture* RwTextureRead(RwChar const* name, RwChar const* maskName) { EAXJMP(0x5A7580); }
+WRAPPER RwBool RwTextureRasterGenerateMipmaps(RwRaster* raster, RwImage* image) { EAXJMP(0x5A7780); }
+WRAPPER RwImage* RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth) { EAXJMP(0x5A9120); }
+WRAPPER RwBool RwImageDestroy(RwImage* image) { EAXJMP(0x5A9180); }
+WRAPPER RwImage* RwImageAllocatePixels(RwImage* image) { EAXJMP(0x5A91E0); }
+WRAPPER RwImage* RwImageFreePixels(RwImage* image) { EAXJMP(0x5A92A0); }
+WRAPPER RwImage* RwImageMakeMask(RwImage* image) { EAXJMP(0x5A92D0); }
+WRAPPER RwImage* RwImageApplyMask(RwImage* image, RwImage const* mask) { EAXJMP(0x5A93A0); }
+WRAPPER RwChar const* RwImageSetPath(RwChar const* path) { EAXJMP(0x5A9750); }
+WRAPPER RwImage* RwImageRead(RwChar const* imageName) { EAXJMP(0x5A9810); }
+WRAPPER RwChar const* RwImageFindFileType(RwChar const* imageName) { EAXJMP(0x5A9B40); }
+WRAPPER RwImage* RwImageReadMaskedImage(RwChar const* imageName, RwChar const* maskname) { EAXJMP(0x5A9C10); }
+WRAPPER RwImage* RwImageCopy(RwImage* destImage, RwImage const* sourceImage) { EAXJMP(0x5A9F50); }
+WRAPPER RwImage* RwImageGammaCorrect(RwImage* image) { EAXJMP(0x5AA130); }
+WRAPPER RwBool RwImageSetGamma(RwReal gammaValue) { EAXJMP(0x5AA2C0); }
+WRAPPER RwStream* _rwStreamWriteVersionedChunkHeader(RwStream* stream, RwInt32 type, RwInt32 size, RwUInt32 version, RwUInt32 buildNum) { EAXJMP(0x5AA4E0); }
+WRAPPER RwBool RwStreamFindChunk(RwStream* stream, RwUInt32 type, RwUInt32* lengthOut, RwUInt32* versionOut) { EAXJMP(0x5AA540); }
+WRAPPER void* RwMemLittleEndian32(void* mem, RwUInt32 size) { EAXJMP(0x5AA640); }
+WRAPPER void* RwMemNative32(void* mem, RwUInt32 size) { EAXJMP(0x5AA650); }
+WRAPPER void* RwMemFloat32ToReal(void* mem, RwUInt32 size) { EAXJMP(0x5AA660); }
+WRAPPER RwStream* RwStreamWriteReal(RwStream* stream, RwReal const* reals, RwUInt32 numBytes) { EAXJMP(0x5AA680); }
+WRAPPER RwStream* RwStreamWriteInt32(RwStream* stream, RwInt32 const* ints, RwUInt32 numBytes) { EAXJMP(0x5AA720); }
+WRAPPER RwStream* RwStreamReadReal(RwStream* stream, RwReal* reals, RwUInt32 numBytes) { EAXJMP(0x5AA740); }
+WRAPPER RwStream* RwStreamReadInt32(RwStream* stream, RwInt32* ints, RwUInt32 numBytes) { EAXJMP(0x5AA7B0); }
+WRAPPER RwUInt32 RwTextureStreamGetSize(RwTexture const* texture) { EAXJMP(0x5AA800); }
+WRAPPER RwTexture const* RwTextureStreamWrite(RwTexture const* texture, RwStream* stream) { EAXJMP(0x5AA870); }
+WRAPPER RwTexture* RwTextureStreamRead(RwStream* stream) { EAXJMP(0x5AAA40); }
+WRAPPER RwTexDictionary const* RwTexDictionaryStreamWrite(RwTexDictionary const* texDict, RwStream* stream) { EAXJMP(0x5AB020); }
+WRAPPER RpMorphTarget const* RpMorphTargetCalcBoundingSphere(RpMorphTarget const* morphTarget, RwSphere* boundingSphere) { EAXJMP(0x5AC890); }
+WRAPPER RwInt32 RpGeometryAddMorphTargets(RpGeometry* geometry, RwInt32 mtcount) { EAXJMP(0x5AC9A0); }
+WRAPPER RpGeometry const* RpGeometryTriangleSetVertexIndices(RpGeometry const* geometry, RpTriangle* triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3) { EAXJMP(0x5ACB60); }
+WRAPPER RpGeometry* RpGeometryTriangleSetMaterial(RpGeometry* geometry, RpTriangle* triangle, RpMaterial* material) { EAXJMP(0x5ACB90); }
+WRAPPER RpGeometry* RpGeometryForAllMaterials(RpGeometry* geometry, RpMaterialCallBack fpCallBack, void* pData) { EAXJMP(0x5ACBF0); }
+WRAPPER RpGeometry* RpGeometryLock(RpGeometry* geometry, RwInt32 lockMode) { EAXJMP(0x5ACC30); }
+WRAPPER RpGeometry* RpGeometryUnlock(RpGeometry* geometry) { EAXJMP(0x5ACC60); }
+WRAPPER RpGeometry* RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format) { EAXJMP(0x5ACD10); }
+WRAPPER RpGeometry* _rpGeometryAddRef(RpGeometry* geometry) { EAXJMP(0x5ACF40); }
+WRAPPER RwBool RpGeometryDestroy(RpGeometry* geometry) { EAXJMP(0x5ACF50); }
+WRAPPER RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5ACFF0); }
+WRAPPER RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5AD020); }
+WRAPPER RpGeometry* RpGeometryStreamRead(RwStream* stream) { EAXJMP(0x5AD050); }
+WRAPPER RwRaster* RwRasterGetCurrentContext() { EAXJMP(0x5AD6D0); }
+WRAPPER RwRaster* RwRasterUnlock(RwRaster* raster) { EAXJMP(0x5AD6F0); }
+WRAPPER RwRaster* RwRasterRenderFast(RwRaster* raster, RwInt32 x, RwInt32 y) { EAXJMP(0x5AD710); }
+WRAPPER RwRaster* RwRasterUnlockPalette(RwRaster* raster) { EAXJMP(0x5AD750); }
+WRAPPER RwBool RwRasterDestroy(RwRaster* raster) { EAXJMP(0x5AD780); }
+WRAPPER RwRaster* RwRasterPushContext(RwRaster* raster) { EAXJMP(0x5AD7C0); }
+WRAPPER RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5AD810); }
+WRAPPER RwUInt8* RwRasterLockPalette(RwRaster* raster, RwInt32 lockMode) { EAXJMP(0x5AD840); }
+WRAPPER RwRaster* RwRasterPopContext() { EAXJMP(0x5AD870); }
+WRAPPER RwInt32 RwRasterGetNumLevels(RwRaster* raster) { EAXJMP(0x5AD8C0); }
+WRAPPER RwRaster* RwRasterShowRaster(RwRaster* raster, void* dev, RwUInt32 flags) { EAXJMP(0x5AD900); }
+WRAPPER RwRaster* RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags) { EAXJMP(0x5AD930); }
+WRAPPER RwUInt8* RwRasterLock(RwRaster* raster, RwUInt8 level, RwInt32 lockMode) { EAXJMP(0x5AD9D0); }
+WRAPPER RpMaterial* RpMaterialCreate() { EAXJMP(0x5ADC30); }
+WRAPPER RwBool RpMaterialDestroy(RpMaterial* material) { EAXJMP(0x5ADCB0); }
+WRAPPER RpMaterial* RpMaterialSetTexture(RpMaterial* material, RwTexture* texture) { EAXJMP(0x5ADD10); }
+WRAPPER RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5ADD40); }
+WRAPPER RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5ADD70); }
+WRAPPER RpMaterial* RpMaterialStreamRead(RwStream* stream) { EAXJMP(0x5ADDA0); }
+WRAPPER RpWorldSector* _rpSectorDefaultRenderCallBack(RpWorldSector* sector) { EAXJMP(0x5AE0B0); }
+WRAPPER RwBool _rpWorldForAllGlobalLights(RpLightCallBack callBack, void* pData) { EAXJMP(0x5AE100); }
+WRAPPER RpWorldSector* _rpWorldSectorForAllLocalLights(RpWorldSector* sector, RpLightCallBack callBack, void* pData) { EAXJMP(0x5AE150); }
+WRAPPER RpWorld* RpWorldUnlock(RpWorld* world) { EAXJMP(0x5AE190); }
+WRAPPER RpWorld* RpWorldSectorGetWorld(RpWorldSector const* sector) { EAXJMP(0x5AE2B0); }
+WRAPPER RwBool RpWorldDestroy(RpWorld* world) { EAXJMP(0x5AE340); }
+WRAPPER RpWorld* RpWorldCreate(RwBBox* boundingBox) { EAXJMP(0x5AE6A0); }
+WRAPPER RwInt32 RpWorldRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5AEA40); }
+WRAPPER RwInt32 RpWorldRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5AEA70); }
+WRAPPER RwBool RpWorldPluginAttach() { EAXJMP(0x5AEAA0); }
+WRAPPER RpWorld* RpWorldAddCamera(RpWorld* world, RwCamera* camera) { EAXJMP(0x5AFB80); }
+WRAPPER RpWorld* RpWorldRemoveCamera(RpWorld* world, RwCamera* camera) { EAXJMP(0x5AFBB0); }
+WRAPPER RpWorld* RpAtomicGetWorld(RpAtomic const* atomic) { EAXJMP(0x5AFC10); }
+WRAPPER RpWorld* RpWorldAddClump(RpWorld* world, RpClump* clump) { EAXJMP(0x5AFC20); }
+WRAPPER RpWorld* RpWorldAddLight(RpWorld* world, RpLight* light) { EAXJMP(0x5AFDA0); }
+WRAPPER RpWorld* RpWorldRemoveLight(RpWorld* world, RpLight* light) { EAXJMP(0x5AFDF0); }
+WRAPPER RwImage* RtBMPImageRead(RwChar const* imageName) { EAXJMP(0x5AFE70); }
+WRAPPER RwBool RpSkinPluginAttach() { EAXJMP(0x5B07D0); }
+WRAPPER RpAtomic* RpSkinAtomicSetHAnimHierarchy(RpAtomic* atomic, RpHAnimHierarchy* hierarchy) { EAXJMP(0x5B1050); }
+WRAPPER RpHAnimHierarchy* RpSkinAtomicGetHAnimHierarchy(RpAtomic const* atomic) { EAXJMP(0x5B1070); }
+WRAPPER RpSkin* RpSkinGeometryGetSkin(RpGeometry* geometry) { EAXJMP(0x5B1080); }
+WRAPPER RpGeometry* RpSkinGeometrySetSkin(RpGeometry* geometry, RpSkin* skin) { EAXJMP(0x5B1090); }
+WRAPPER RwMatrix const* RpSkinGetSkinToBoneMatrices(RpSkin* skin) { EAXJMP(0x5B10D0); }
+WRAPPER RpHAnimHierarchy* RpHAnimHierarchyCreate(RwInt32 numNodes, RwUInt32* nodeFlags, RwInt32* nodeIDs, RpHAnimHierarchyFlag flags, RwInt32 maxKeyFrameSize) { EAXJMP(0x5B10E0); }
+WRAPPER RpHAnimHierarchy* RpHAnimFrameGetHierarchy(RwFrame* frame) { EAXJMP(0x5B11F0); }
+WRAPPER RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy* hierarchy, RpHAnimAnimation* anim) { EAXJMP(0x5B1200); }
+WRAPPER RwBool RpHAnimHierarchySubAnimTime(RpHAnimHierarchy* hierarchy, RwReal time) { EAXJMP(0x5B12B0); }
+WRAPPER RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy* hierarchy, RwReal time) { EAXJMP(0x5B1480); }
+WRAPPER RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy* hierarchy) { EAXJMP(0x5B1780); }
+WRAPPER RpHAnimAnimation* RpHAnimAnimationStreamRead(RwStream* stream) { EAXJMP(0x5B1C10); }
+WRAPPER RwBool RpHAnimPluginAttach() { EAXJMP(0x5B1D50); }
+WRAPPER RwBool RpMatFXPluginAttach() { EAXJMP(0x5B2640); }
+WRAPPER RpAtomic* RpMatFXAtomicEnableEffects(RpAtomic* atomic) { EAXJMP(0x5B3750); }
+WRAPPER RpMaterial* RpMatFXMaterialSetEffects(RpMaterial* material, RpMatFXMaterialFlags flags) { EAXJMP(0x5B3780); }
+WRAPPER RpMaterial* RpMatFXMaterialSetupEnvMap(RpMaterial* material, RwTexture* texture, RwFrame* frame, RwBool useFrameBufferAlpha, RwReal coef) { EAXJMP(0x5B38D0); }
+WRAPPER RpMaterial* RpMatFXMaterialSetBumpMapTexture(RpMaterial* material, RwTexture* texture) { EAXJMP(0x5B3A40); }
+WRAPPER RwBool RwD3D8SetRenderState(RwUInt32 state, RwUInt32 value) { EAXJMP(0x5B3CF0); }
+WRAPPER void RwD3D8GetRenderState(RwUInt32 state, void* value) { EAXJMP(0x5B3D40); }
+WRAPPER RwBool RwD3D8SetTextureStageState(RwUInt32 stage, RwUInt32 type, RwUInt32 value) { EAXJMP(0x5B3D60); }
+WRAPPER RwBool RwD3D8SetTexture(RwTexture* texture, RwUInt32 stage) { EAXJMP(0x5B53A0); }
+WRAPPER void* RwIm3DTransform(RwIm3DVertex* pVerts, RwUInt32 numVerts, RwMatrix* ltm, RwUInt32 flags) { EAXJMP(0x5B6720); }
+WRAPPER RwBool RwIm3DEnd() { EAXJMP(0x5B67F0); }
+WRAPPER RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex* indices, RwInt32 numIndices) { EAXJMP(0x5B6820); }
+WRAPPER RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2) { EAXJMP(0x5B6980); }
+WRAPPER RxPipeline* RwIm3DSetTransformPipeline(RxPipeline* pipeline) { EAXJMP(0x5B6A50); }
+WRAPPER RxPipeline* RwIm3DSetRenderPipeline(RxPipeline* pipeline, RwPrimitiveType primType) { EAXJMP(0x5B6AC0); }
+WRAPPER void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate) { EAXJMP(0x5B95D0); }
+WRAPPER RwBool RwD3D8CameraAttachWindow(void* camera, void* hwnd) { EAXJMP(0x5B9640); }
+WRAPPER RwBool RwD3D8DeviceSupportsDXTTexture() { EAXJMP(0x5BAEB0); }
+WRAPPER RwBool RwD3D8SetVertexShader(RwUInt32 handle) { EAXJMP(0x5BAF90); }
+WRAPPER RwBool RwD3D8SetPixelShader(RwUInt32 handle) { EAXJMP(0x5BAFD0); }
+WRAPPER RwBool RwD3D8SetStreamSource(RwUInt32 streamNumber, void* streamData, RwUInt32 stride) { EAXJMP(0x5BB010); }
+WRAPPER RwBool RwD3D8SetIndices(void* indexData, RwUInt32 baseVertexIndex) { EAXJMP(0x5BB060); }
+WRAPPER RwBool RwD3D8DrawIndexedPrimitive(RwUInt32 primitiveType, RwUInt32 minIndex, RwUInt32 numVertices, RwUInt32 startIndex, RwUInt32 numIndices) { EAXJMP(0x5BB0B0); }
+WRAPPER RwBool RwD3D8DrawPrimitive(RwUInt32 primitiveType, RwUInt32 startVertex, RwUInt32 numVertices) { EAXJMP(0x5BB140); }
+WRAPPER RwBool RwD3D8SetTransform(RwUInt32 state, void const* matrix) { EAXJMP(0x5BB1D0); }
+WRAPPER void RwD3D8GetTransform(RwUInt32 state, void* matrix) { EAXJMP(0x5BB310); }
+WRAPPER RwBool RwD3D8SetTransformWorld(RwMatrix const* matrix) { EAXJMP(0x5BB340); }
+WRAPPER RwBool RwD3D8SetSurfaceProperties(RwRGBA const* color, RwSurfaceProperties const* surfaceProps, RwBool modulate) { EAXJMP(0x5BB490); }
+WRAPPER RwBool RwD3D8SetLight(RwInt32 index, void const* light) { EAXJMP(0x5BB7A0); }
+WRAPPER RwBool RwD3D8EnableLight(RwInt32 index, RwBool enable) { EAXJMP(0x5BB890); }
+WRAPPER RwBool RwD3D8DynamicVertexBufferCreate(RwUInt32 fvf, RwUInt32 size, void** vertexBuffer) { EAXJMP(0x5BB9F0); }
+WRAPPER void RwD3D8DynamicVertexBufferDestroy(void* vertexBuffer) { EAXJMP(0x5BBAE0); }
+WRAPPER RwBool RwD3D8IndexBufferCreate(RwUInt32 numIndices, void** indexBuffer) { EAXJMP(0x5BBB10); }
+WRAPPER RwBool RwD3D8CreatePixelShader(RwUInt32 const* function, RwUInt32* handle) { EAXJMP(0x5BBB40); }
+WRAPPER void RwD3D8DeletePixelShader(RwUInt32 handle) { EAXJMP(0x5BBB90); }
+WRAPPER RwBool RwD3D8SetPixelShaderConstant(RwUInt32 registerAddress, void const* antData, RwUInt32 antCount) { EAXJMP(0x5BBC00); }
+WRAPPER void const* RwD3D8GetCaps() { EAXJMP(0x5BBC30); }
+WRAPPER RwBool RwD3D8CameraIsSphereFullyInsideFrustum(void const* camera, void const* sphere) { EAXJMP(0x5BBC40); }
+WRAPPER RwBool RwD3D8CameraIsBBoxFullyInsideFrustum(void const* camera, void const* boundingBox) { EAXJMP(0x5BBCA0); }
+WRAPPER RwBool RwD3D8DynamicVertexBufferLock(RwUInt32 vertexSize, RwUInt32 numVertex, void** vertexBufferOut, void** vertexDataOut, RwUInt32* baseIndexOut) { EAXJMP(0x5BBD30); }
+WRAPPER RwBool RwD3D8DynamicVertexBufferUnlock(void* vertexBuffer) { EAXJMP(0x5BBEB0); }
+WRAPPER RwBool _rwIntelSSEsupported() { EAXJMP(0x5BBED0); }
+WRAPPER RwImage* RwImageSetFromRaster(RwImage* image, RwRaster* raster) { EAXJMP(0x5BBF10); }
+WRAPPER RwRaster* RwRasterSetFromImage(RwRaster* raster, RwImage* image) { EAXJMP(0x5BBF50); }
+WRAPPER RwImage* RwImageFindRasterFormat(RwImage* ipImage, RwInt32 nRasterType, RwInt32* npWidth, RwInt32* npHeight, RwInt32* npDepth, RwInt32* npFormat) { EAXJMP(0x5BBF80); }
+WRAPPER RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5BBFF0); }
+WRAPPER rwFrameList* _rwFrameListDeinitialize(rwFrameList* frameList) { EAXJMP(0x5BC020); }
+WRAPPER rwFrameList* _rwFrameListStreamRead(RwStream* stream, rwFrameList* fl) { EAXJMP(0x5BC050); }
+WRAPPER RpLight* RpLightSetRadius(RpLight* light, RwReal radius) { EAXJMP(0x5BC300); }
+WRAPPER RpLight* RpLightSetColor(RpLight* light, RwRGBAReal const* color) { EAXJMP(0x5BC320); }
+WRAPPER RwReal RpLightGetConeAngle(RpLight const* light) { EAXJMP(0x5BC370); }
+WRAPPER RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5BC5B0); }
+WRAPPER RpLight* RpLightStreamRead(RwStream* stream) { EAXJMP(0x5BC5E0); }
+WRAPPER RwBool RpLightDestroy(RpLight* light) { EAXJMP(0x5BC780); }
+WRAPPER RpLight* RpLightCreate(RwInt32 type) { EAXJMP(0x5BC7C0); }
+WRAPPER void _rwD3D8TexDictionaryEnableRasterFormatConversion(RwBool enable) { EAXJMP(0x5BE280); }
+WRAPPER RwFileFunctions* RwOsGetFileInterface() { EAXJMP(0x5BF110); }
+WRAPPER RwBool RwFreeListDestroy(RwFreeList* freelist) { EAXJMP(0x5C1720); }
+WRAPPER RwFreeList* RwFreeListCreate(RwInt32 entrySize, RwInt32 entriesPerBlock, RwInt32 alignment) { EAXJMP(0x5C1790); }
+WRAPPER RwInt32 RwFreeListPurge(RwFreeList* freelist) { EAXJMP(0x5C19F0); }
+WRAPPER RwInt32 RwFreeListPurgeAllFreeLists() { EAXJMP(0x5C1B90); }
+WRAPPER RwFreeList* RwFreeListForAllUsed(RwFreeList* freelist, RwFreeListCallBack fpCallBack, void* pData) { EAXJMP(0x5C1D40); }
+WRAPPER RwBool _rxPipelineClose() { EAXJMP(0x5C2780); }
+WRAPPER RwBool _rxPipelineOpen() { EAXJMP(0x5C27E0); }
+WRAPPER RxHeap* RxHeapGetGlobalHeap() { EAXJMP(0x5C2AD0); }
+WRAPPER RxPacket* RxPacketCreate(RxPipelineNode* node) { EAXJMP(0x5C2AE0); }
+WRAPPER RxCluster* RxClusterSetExternalData(RxCluster* cluster, void* data, RwInt32 stride, RwInt32 numElements) { EAXJMP(0x5C2B10); }
+WRAPPER RxCluster* RxClusterSetData(RxCluster* cluster, void* data, RwInt32 stride, RwInt32 numElements) { EAXJMP(0x5C2B70); }
+WRAPPER RxCluster* RxClusterInitializeData(RxCluster* cluster, RwUInt32 numElements, RwUInt16 stride) { EAXJMP(0x5C2BD0); }
+WRAPPER RxCluster* RxClusterResizeData(RxCluster* CurrentCluster, RwUInt32 NumElements) { EAXJMP(0x5C2C40); }
+WRAPPER RxCluster* RxClusterLockWrite(RxPacket* packet, RwUInt32 clusterIndex, RxPipelineNode* node) { EAXJMP(0x5C2C90); }
+WRAPPER RxPipeline* RxPipelineExecute(RxPipeline* pipeline, void* data, RwBool heapReset) { EAXJMP(0x5C2D60); }
+WRAPPER RxPipeline* RxPipelineCreate() { EAXJMP(0x5C2E00); }
+WRAPPER void _rxPipelineDestroy(RxPipeline* Pipeline) { EAXJMP(0x5C2E70); }
+WRAPPER RwBool RwResourcesFreeResEntry(RwResEntry* entry) { EAXJMP(0x5C3080); }
+WRAPPER void _rwResourcesPurge() { EAXJMP(0x5C30F0); }
+WRAPPER RwResEntry* RwResourcesAllocateResEntry(void* owner, RwResEntry** ownerRef, RwInt32 size, RwResEntryDestroyNotify destroyNotify) { EAXJMP(0x5C3170); }
+WRAPPER RwBool RwResourcesEmptyArena() { EAXJMP(0x5C3360); }
+WRAPPER RwBool _rwPluginRegistryOpen() { EAXJMP(0x5C3450); }
+WRAPPER RwBool _rwPluginRegistryClose() { EAXJMP(0x5C3480); }
+WRAPPER RwInt32 _rwPluginRegistryGetPluginOffset(RwPluginRegistry const* reg, RwUInt32 pluginID) { EAXJMP(0x5C3590); }
+WRAPPER RwInt32 _rwPluginRegistryAddPlugin(RwPluginRegistry* reg, RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5C35C0); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryInitObject(RwPluginRegistry const* reg, void* object) { EAXJMP(0x5C37F0); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryDeInitObject(RwPluginRegistry const* reg, void* object) { EAXJMP(0x5C3850); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryCopyObject(RwPluginRegistry const* reg, void* dstObject, void const* srcObject) { EAXJMP(0x5C3880); }
+WRAPPER RwError* RwErrorSet(RwError* code) { EAXJMP(0x5C3910); }
+WRAPPER RwInt32 _rwerror(RwInt32 code, ...) { EAXJMP(0x5C3970); }
+WRAPPER RwInt32 _rwPluginRegistryAddPluginStream(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5C3980); }
+WRAPPER RwInt32 _rwPluginRegistryAddPlgnStrmlwysCB(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB) { EAXJMP(0x5C39C0); }
+WRAPPER RwInt32 _rwPluginRegistryAddPlgnStrmRightsCB(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB) { EAXJMP(0x5C39F0); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryReadDataChunks(RwPluginRegistry const* reg, RwStream* stream, void* object) { EAXJMP(0x5C3A20); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryInvokeRights(RwPluginRegistry const* reg, RwUInt32 id, void* obj, RwUInt32 extraData) { EAXJMP(0x5C3B50); }
+WRAPPER RwInt32 _rwPluginRegistryGetSize(RwPluginRegistry const* reg, void const* object) { EAXJMP(0x5C3BA0); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistryWriteDataChunks(RwPluginRegistry const* reg, RwStream* stream, void const* object) { EAXJMP(0x5C3BE0); }
+WRAPPER RwPluginRegistry const* _rwPluginRegistrySkipDataChunks(RwPluginRegistry const* reg, RwStream* stream) { EAXJMP(0x5C3CB0); }
+WRAPPER RwCamera* RwCameraStreamRead(RwStream* stream) { EAXJMP(0x5C3D30); }
+WRAPPER RwBBox* RwBBoxCalculate(RwBBox* boundBox, RwV3d const* verts, RwInt32 numVerts) { EAXJMP(0x5C5570); }
+WRAPPER RwImage* RwImageResample(RwImage* dstImage, RwImage const* srcImage) { EAXJMP(0x5C72B0); }
+WRAPPER RwImage* RwImageCreateResample(RwImage const* srcImage, RwInt32 width, RwInt32 height) { EAXJMP(0x5C7B30); }
+WRAPPER RxRenderStateVector* RxRenderStateVectorSetDefaultRenderStateVector(RxRenderStateVector* rsvp) { EAXJMP(0x5D9240); }
+WRAPPER RxRenderStateVector* RxRenderStateVectorCreate(RwBool current) { EAXJMP(0x5D9340); }
+WRAPPER void RxRenderStateVectorDestroy(RxRenderStateVector* rsvp) { EAXJMP(0x5D9410); }
+WRAPPER RxRenderStateVector* RxRenderStateVectorLoadDriverState(RxRenderStateVector* rsvp) { EAXJMP(0x5D9460); }
+WRAPPER void _rxEmbeddedPacketBetweenPipelines(RxPipeline* fromPipeline, RxPipeline* toPipeline) { EAXJMP(0x5D95D0); }
+WRAPPER RxPipelineNode* _rxEmbeddedPacketBetweenNodes(RxPipeline* pipeline, RxPipelineNode* nodeFrom, RwUInt32 whichOutput) { EAXJMP(0x5D9740); }
+WRAPPER void _rxPacketDestroy(RxPacket* Packet) { EAXJMP(0x5D9810); }
+WRAPPER RpMaterialList* _rpMaterialListDeinitialize(RpMaterialList* matList) { EAXJMP(0x5C8B10); }
+WRAPPER RpMaterialList* _rpMaterialListInitialize(RpMaterialList* matList) { EAXJMP(0x5C8B70); }
+WRAPPER RpMaterial* _rpMaterialListGetMaterial(RpMaterialList const* matList, RwInt32 matIndex) { EAXJMP(0x5C8B80); }
+WRAPPER RwInt32 _rpMaterialListAppendMaterial(RpMaterialList* matList, RpMaterial* material) { EAXJMP(0x5C8B90); }
+WRAPPER RwInt32 _rpMaterialListFindMaterialIndex(RpMaterialList const* matList, RpMaterial const* material) { EAXJMP(0x5C8C50); }
+WRAPPER RpMaterialList* _rpMaterialListStreamRead(RwStream* stream, RpMaterialList* matList) { EAXJMP(0x5C8C80); }
+WRAPPER RpMeshHeader* _rpMeshHeaderCreate(RwUInt32 size) { EAXJMP(0x5C8FE0); }
+WRAPPER void* _rpMeshClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5C8FF0); }
+WRAPPER void* _rpMeshOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5C9020); }
+WRAPPER RpBuildMesh* _rpBuildMeshCreate(RwUInt32 bufferSize) { EAXJMP(0x5C9140); }
+WRAPPER RwBool _rpBuildMeshDestroy(RpBuildMesh* mesh) { EAXJMP(0x5C9220); }
+WRAPPER RwBool _rpMeshDestroy(RpMeshHeader* mesh) { EAXJMP(0x5C9260); }
+WRAPPER RpBuildMesh* _rpBuildMeshAddTriangle(RpBuildMesh* mesh, RpMaterial* material, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3) { EAXJMP(0x5C92A0); }
+WRAPPER RpMeshHeader* _rpMeshHeaderForAllMeshes(RpMeshHeader* meshHeader, RpMeshCallBack fpCallBack, void* pData) { EAXJMP(0x5C9380); }
+WRAPPER RwStream* _rpMeshWrite(RpMeshHeader const* meshHeader, void const* object, RwStream* stream, RpMaterialList const* matList) { EAXJMP(0x5C93C0); }
+WRAPPER RpMeshHeader* _rpMeshRead(RwStream* stream, void const* object, RpMaterialList const* matList) { EAXJMP(0x5C9510); }
+WRAPPER RwInt32 _rpMeshSize(RpMeshHeader const* meshHeader, void const* object) { EAXJMP(0x5C96E0); }
+WRAPPER RpMeshHeader* RpBuildMeshGenerateDefaultTriStrip(RpBuildMesh* buildmesh, void* data) { EAXJMP(0x5C9730); }
+WRAPPER RpMeshHeader* _rpTriListMeshGenerate(RpBuildMesh* buildMesh, void* data) { EAXJMP(0x5CAE10); }
+WRAPPER RpMeshHeader* _rpMeshOptimise(RpBuildMesh* buildmesh, RwUInt32 flags) { EAXJMP(0x5CB230); }
+WRAPPER RwInt32 RpWorldSectorRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5CB2B0); }
+WRAPPER RwInt32 RpWorldSectorRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5CB2E0); }
+WRAPPER RxPipeline* RpWorldSetDefaultSectorPipeline(RxPipeline* pipeline) { EAXJMP(0x5CB630); }
+WRAPPER RxPipeline* RpAtomicSetDefaultPipeline(RxPipeline* pipeline) { EAXJMP(0x5CB670); }
+WRAPPER void RpHAnimStdKeyFrameToMatrix(RwMatrix* matrix, void* voidIFrame) { EAXJMP(0x5CDEE0); }
+WRAPPER void RpHAnimStdKeyFrameInterpolate(void* voidOut, void* voidIn1, void* voidIn2, RwReal time) { EAXJMP(0x5CE000); }
+WRAPPER void RpHAnimStdKeyFrameBlend(void* voidOut, void* voidIn1, void* voidIn2, RwReal alpha) { EAXJMP(0x5CE420); }
+WRAPPER RpHAnimAnimation* RpHAnimStdKeyFrameStreamRead(RwStream* stream, RpHAnimAnimation* animation) { EAXJMP(0x5CE820); }
+WRAPPER RwBool RpHAnimStdKeyFrameStreamWrite(RpHAnimAnimation* animation, RwStream* stream) { EAXJMP(0x5CE8C0); }
+WRAPPER RwInt32 RpHAnimStdKeyFrameStreamGetSize(RpHAnimAnimation* animation) { EAXJMP(0x5CE930); }
+WRAPPER void RpHAnimStdKeyFrameMulRecip(void* voidFrame, void* voidStart) { EAXJMP(0x5CE950); }
+WRAPPER void RpHAnimStdKeyFrameAdd(void* voidOut, void* voidIn1, void* voidIn2) { EAXJMP(0x5CEAB0); }
+WRAPPER void RxHeapFree(RxHeap* heap, void* block) { EAXJMP(0x5D1070); }
+WRAPPER void* RxHeapAlloc(RxHeap* heap, RwUInt32 size) { EAXJMP(0x5D1260); }
+WRAPPER void* RxHeapRealloc(RxHeap* heap, void* block, RwUInt32 newSize, RwBool allowCopy) { EAXJMP(0x5D14D0); }
+WRAPPER RwBool _rxHeapReset(RxHeap* heap) { EAXJMP(0x5D1680); }
+WRAPPER void RxHeapDestroy(RxHeap* heap) { EAXJMP(0x5D16F0); }
+WRAPPER RxHeap* RxHeapCreate(RwUInt32 size) { EAXJMP(0x5D1750); }
+WRAPPER RxNodeOutput RxPipelineNodeFindOutputByName(RxPipelineNode* node, RwChar const* outputname) { EAXJMP(0x5D1EC0); }
+WRAPPER RxNodeInput RxPipelineNodeFindInput(RxPipelineNode* node) { EAXJMP(0x5D1F20); }
+WRAPPER RxPipeline* RxPipelineNodeRequestCluster(RxPipeline* pipeline, RxPipelineNode* node, RxClusterDefinition* clusterDef) { EAXJMP(0x5D1F30); }
+WRAPPER RxPipeline* RxLockedPipeUnlock(RxLockedPipe* pipeline) { EAXJMP(0x5D1FA0); }
+WRAPPER RxLockedPipe* RxPipelineLock(RxPipeline* pipeline) { EAXJMP(0x5D29F0); }
+WRAPPER RxPipelineNode* RxPipelineFindNodeByName(RxPipeline* pipeline, RwChar const* name, RxPipelineNode* start, RwInt32* nodeIndex) { EAXJMP(0x5D2B10); }
+WRAPPER RxLockedPipe* RxLockedPipeAddFragment(RxLockedPipe *pipeline, RwUInt32 *firstIndex, RxNodeDefinition *nodeDef0, ...) { EAXJMP(0x5D2BA0); }
+WRAPPER RxPipeline* RxLockedPipeAddPath(RxLockedPipe* pipeline, RxNodeOutput out, RxNodeInput in) { EAXJMP(0x5D2EE0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmRenderSetup() { EAXJMP(0x5D31C0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmMangleTriangleIndices() { EAXJMP(0x5D35C0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetCullTriangle() { EAXJMP(0x5D3C60); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetClipTriangle() { EAXJMP(0x5D4F80); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetSubmitTriangle() { EAXJMP(0x5D51C0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmInstance() { EAXJMP(0x5D5400); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetTransform() { EAXJMP(0x5D6000); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmStash() { EAXJMP(0x5D61C0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmMangleLineIndices() { EAXJMP(0x5D6470); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetClipLine() { EAXJMP(0x5D7230); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetSubmitLine() { EAXJMP(0x5D74C0); }
+WRAPPER RwBool _rwD3D8LightsOpen() { EAXJMP(0x5D9C90); }
+WRAPPER void _rwD3D8LightsClose() { EAXJMP(0x5D9EF0); }
+WRAPPER RwBool _rwD3D8LightsGlobalEnable(RpLightFlag flags) { EAXJMP(0x5D9F80); }
+WRAPPER RwBool _rwD3D8LightLocalEnable(RpLight* light) { EAXJMP(0x5DA210); }
+WRAPPER void _rwD3D8LightsEnable(RwBool enable, RwUInt32 type) { EAXJMP(0x5DA450); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetD3D8WorldSectorAllInOne() { EAXJMP(0x5DAAC0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetD3D8AtomicAllInOne() { EAXJMP(0x5DC500); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetWorldSectorInstance() { EAXJMP(0x5DCC50); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetWorldSectorEnumerateLights() { EAXJMP(0x5DCD80); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetAtomicInstance() { EAXJMP(0x5DD800); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetAtomicEnumerateLights() { EAXJMP(0x5DD9B0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetMaterialScatter() { EAXJMP(0x5DDAA0); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetLight() { EAXJMP(0x5DF040); }
+WRAPPER RxNodeDefinition* RxNodeDefinitionGetPostLight() { EAXJMP(0x5DF560); }
+WRAPPER void RxD3D8AllInOneSetRenderCallBack(RxPipelineNode* node, RxD3D8AllInOneRenderCallBack callback) { EAXJMP(0x5DFC60); } \ No newline at end of file
diff --git a/src/core/templates.h b/src/core/templates.h
new file mode 100644
index 00000000..ef2db33a
--- /dev/null
+++ b/src/core/templates.h
@@ -0,0 +1,205 @@
+#pragma once
+
+template<typename T, int n>
+class CStore
+{
+public:
+ int allocPtr;
+ T store[n];
+
+ T *alloc(void){
+ if(this->allocPtr >= n){
+ printf("Size of this thing:%d needs increasing\n", n);
+ assert(0);
+ }
+ return &this->store[this->allocPtr++];
+ }
+ void clear(void){
+ this->allocPtr = 0;
+ }
+};
+
+template<typename T, typename U = T>
+class CPool
+{
+ U *m_entries;
+ union Flags {
+ struct {
+ uint8 id : 7;
+ uint8 free : 1;
+ };
+ uint8 u;
+ } *m_flags;
+ int m_size;
+ int m_allocPtr;
+
+public:
+ CPool(int size){
+ m_entries = (U*)malloc(sizeof(U)*size);
+ m_flags = (Flags*)malloc(sizeof(Flags)*size);
+ m_size = size;
+ m_allocPtr = 0;
+ for(int i = 0; i < size; i++){
+ m_flags[i].id = 0;
+ m_flags[i].free = 1;
+ }
+ }
+ int GetSize(void) { return m_size; }
+ T *New(void){
+ bool wrapped = false;
+ do
+ if(++m_allocPtr == m_size){
+ if(wrapped)
+ return nil;
+ wrapped = true;
+ m_allocPtr = 0;
+ }
+ while(!m_flags[m_allocPtr].free);
+ m_flags[m_allocPtr].free = 0;
+ m_flags[m_allocPtr].id++;
+ return (T*)&m_entries[m_allocPtr];
+ }
+ T *New(int handle){
+ T *entry = (T*)&m_entries[handle>>8];
+ SetNotFreeAt(handle);
+ return entry;
+ }
+ void SetNotFreeAt(int handle){
+ int idx = handle>>8;
+ m_flags[idx].free = 0;
+ m_flags[idx].id = handle & 0x7F;
+ for(m_allocPtr = 0; m_allocPtr < m_size; m_allocPtr++)
+ if(m_flags[m_allocPtr].free)
+ return;
+ }
+ void Delete(T *entry){
+ int i = GetJustIndex(entry);
+ m_flags[i].free = 1;
+ if(i < m_allocPtr)
+ m_allocPtr = i;
+ }
+ T *GetSlot(int i){
+ return m_flags[i].free ? nil : (T*)&m_entries[i];
+ }
+ T *GetAt(int handle){
+ return m_flags[handle>>8].u == (handle & 0xFF) ?
+ (T*)&m_entries[handle >> 8] : nil;
+ }
+ int GetIndex(T *entry){
+ int i = GetJustIndex(entry);
+ return m_flags[i].u + (i<<8);
+ }
+ int GetJustIndex(T *entry){
+ // TODO: the cast is unsafe
+ return (int)((U*)entry - m_entries);
+ }
+ int GetNoOfUsedSpaces(void){
+ int i;
+ int n = 0;
+ for(i = 0; i < m_size; i++)
+ if(!m_flags[i].free)
+ n++;
+ return n;
+ }
+ void ClearStorage(uint8 *&flags, U *&entries){
+ free(flags);
+ free(entries);
+ flags = nil;
+ entries = nil;
+ }
+ void CopyBack(uint8 *&flags, U *&entries){
+ memcpy(m_flags, flags, sizeof(uint8)*m_size);
+ memcpy(m_entries, entries, sizeof(U)*m_size);
+ debug("Size copied:%d (%d)\n", sizeof(U)*m_size, sizeof(Flags)*m_size);
+ m_allocPtr = 0;
+ ClearStorage(flags, entries);
+ debug("CopyBack:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */
+ }
+ void Store(uint8 *&flags, U *&entries){
+ flags = (uint8*)malloc(sizeof(uint8)*m_size);
+ entries = (U*)malloc(sizeof(U)*m_size);
+ memcpy(flags, m_flags, sizeof(uint8)*m_size);
+ memcpy(entries, m_entries, sizeof(U)*m_size);
+ debug("Stored:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */
+ }
+};
+
+template<typename T>
+class CLink
+{
+public:
+ T item;
+ CLink<T> *prev;
+ CLink<T> *next;
+
+ void Insert(CLink<T> *link){
+ link->next = this->next;
+ this->next->prev = link;
+ link->prev = this;
+ this->next = link;
+ }
+ void Remove(void){
+ this->prev->next = this->next;
+ this->next->prev = this->prev;
+ }
+};
+
+template<typename T>
+class CLinkList
+{
+public:
+ CLink<T> head, tail;
+ CLink<T> freeHead, freeTail;
+ CLink<T> *links;
+
+ void Init(int n){
+ links = new CLink<T>[n];
+ head.next = &tail;
+ tail.prev = &head;
+ freeHead.next = &freeTail;
+ freeTail.prev = &freeHead;
+ while(n--)
+ freeHead.Insert(&links[n]);
+ }
+ void Shutdown(void){
+ delete[] links;
+ links = nil;
+ }
+ void Clear(void){
+ while(head.next != &tail)
+ Remove(head.next);
+ }
+ CLink<T> *Insert(T const &item){
+ CLink<T> *node = freeHead.next;
+ if(node == &freeTail)
+ return nil;
+ node->item = item;
+ node->Remove(); // remove from free list
+ head.Insert(node);
+ return node;
+ }
+ CLink<T> *InsertSorted(T const &item){
+ CLink<T> *sort;
+ for(sort = head.next; sort != &tail; sort = sort->next)
+ if(sort->item.sort >= item.sort)
+ break;
+ CLink<T> *node = freeHead.next;
+ if(node == &freeTail)
+ return nil;
+ node->item = item;
+ node->Remove(); // remove from free list
+ sort->prev->Insert(node);
+ return node;
+ }
+ void Remove(CLink<T> *link){
+ link->Remove(); // remove from list
+ freeHead.Insert(link); // insert into free list
+ }
+ int Count(void){
+ int n = 0;
+ CLink<T> *lnk;
+ for(lnk = head.next; lnk != &tail; lnk = lnk->next)
+ n++;
+ return n;
+ }
+};