summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Camera.cpp63
-rw-r--r--src/Camera.h420
-rw-r--r--src/Clock.cpp116
-rw-r--r--src/Clock.h27
-rw-r--r--src/Collision.cpp1629
-rw-r--r--src/Collision.h152
-rw-r--r--src/CullZones.cpp323
-rw-r--r--src/CullZones.h93
-rw-r--r--src/Game.cpp5
-rw-r--r--src/Game.h15
-rw-r--r--src/General.h15
-rw-r--r--src/Glass.cpp15
-rw-r--r--src/Glass.h10
-rw-r--r--src/Lists.cpp26
-rw-r--r--src/Lists.h130
-rw-r--r--src/MenuManager.cpp4
-rw-r--r--src/MenuManager.h5
-rw-r--r--src/NodeName.cpp15
-rw-r--r--src/NodeName.h3
-rw-r--r--src/Pad.cpp37
-rw-r--r--src/Pad.h116
-rw-r--r--src/ParticleObject.cpp5
-rw-r--r--src/ParticleObject.h31
-rw-r--r--src/PathFind.cpp591
-rw-r--r--src/PathFind.h130
-rw-r--r--src/Placeable.cpp81
-rw-r--r--src/Placeable.h26
-rw-r--r--src/Pools.cpp19
-rw-r--r--src/Pools.h34
-rw-r--r--src/References.cpp22
-rw-r--r--src/References.h17
-rw-r--r--src/RwHelper.cpp19
-rw-r--r--src/RwHelper.h3
-rw-r--r--src/Streaming.cpp10
-rw-r--r--src/Streaming.h54
-rw-r--r--src/SurfaceTable.cpp44
-rw-r--r--src/SurfaceTable.h99
-rw-r--r--src/Timecycle.cpp92
-rw-r--r--src/Timecycle.h111
-rw-r--r--src/Timer.cpp14
-rw-r--r--src/Timer.h19
-rw-r--r--src/TxdStore.cpp158
-rw-r--r--src/TxdStore.h34
-rw-r--r--src/Weather.cpp27
-rw-r--r--src/Weather.h35
-rw-r--r--src/World.cpp39
-rw-r--r--src/World.h66
-rw-r--r--src/Zones.cpp614
-rw-r--r--src/Zones.h107
-rw-r--r--src/common.h97
-rw-r--r--src/config.h56
-rw-r--r--src/debugmenu_public.h154
-rw-r--r--src/entities/Building.cpp7
-rw-r--r--src/entities/Building.h15
-rw-r--r--src/entities/CutsceneHead.cpp2
-rw-r--r--src/entities/CutsceneHead.h10
-rw-r--r--src/entities/CutsceneObject.cpp2
-rw-r--r--src/entities/CutsceneObject.h9
-rw-r--r--src/entities/Entity.cpp391
-rw-r--r--src/entities/Entity.h146
-rw-r--r--src/entities/Object.cpp9
-rw-r--r--src/entities/Object.h50
-rw-r--r--src/entities/Ped.h35
-rw-r--r--src/entities/Physical.cpp916
-rw-r--r--src/entities/Physical.h137
-rw-r--r--src/entities/Treadable.cpp7
-rw-r--r--src/entities/Treadable.h16
-rw-r--r--src/entities/Vehicle.h21
-rw-r--r--src/main.cpp98
-rw-r--r--src/math/Matrix.h245
-rw-r--r--src/math/Rect.h31
-rw-r--r--src/math/Vector.h82
-rw-r--r--src/math/Vector2D.h37
-rw-r--r--src/modelinfo/BaseModelInfo.cpp117
-rw-r--r--src/modelinfo/BaseModelInfo.h66
-rw-r--r--src/modelinfo/ClumpModelInfo.cpp156
-rw-r--r--src/modelinfo/ClumpModelInfo.h60
-rw-r--r--src/modelinfo/ModelIndices.cpp32
-rw-r--r--src/modelinfo/ModelIndices.h224
-rw-r--r--src/modelinfo/ModelInfo.cpp124
-rw-r--r--src/modelinfo/ModelInfo.h35
-rw-r--r--src/modelinfo/PedModelInfo.cpp197
-rw-r--r--src/modelinfo/PedModelInfo.h47
-rw-r--r--src/modelinfo/SimpleModelInfo.cpp174
-rw-r--r--src/modelinfo/SimpleModelInfo.h57
-rw-r--r--src/modelinfo/TimeModelInfo.cpp36
-rw-r--r--src/modelinfo/TimeModelInfo.h18
-rw-r--r--src/modelinfo/VehicleModelInfo.cpp917
-rw-r--r--src/modelinfo/VehicleModelInfo.h115
-rw-r--r--src/patcher.cpp22
-rw-r--r--src/patcher.h171
-rw-r--r--src/render/2dEffect.h39
-rw-r--r--src/render/Clouds.cpp430
-rw-r--r--src/render/Clouds.h20
-rw-r--r--src/render/Coronas.cpp10
-rw-r--r--src/render/Coronas.h13
-rw-r--r--src/render/Draw.cpp6
-rw-r--r--src/render/Draw.h16
-rw-r--r--src/render/Lights.cpp171
-rw-r--r--src/render/Lights.h9
-rw-r--r--src/render/Particle.cpp10
-rw-r--r--src/render/Particle.h82
-rw-r--r--src/render/RenderBuffer.cpp59
-rw-r--r--src/render/RenderBuffer.h10
-rw-r--r--src/render/Renderer.cpp1165
-rw-r--r--src/render/Renderer.h59
-rw-r--r--src/render/Sprite.cpp553
-rw-r--r--src/render/Sprite.h37
-rw-r--r--src/render/VisibilityPlugins.cpp849
-rw-r--r--src/render/VisibilityPlugins.h129
-rw-r--r--src/rw.cpp285
-rw-r--r--src/templates.h179
112 files changed, 14992 insertions, 0 deletions
diff --git a/src/Camera.cpp b/src/Camera.cpp
new file mode 100644
index 00000000..f4cea966
--- /dev/null
+++ b/src/Camera.cpp
@@ -0,0 +1,63 @@
+#include "common.h"
+#include "patcher.h"
+#include "Draw.h"
+#include "Camera.h"
+
+CCamera &TheCamera = *(CCamera*)0x6FACF8;
+
+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;
+}
+
+
+STARTPATCHES
+ InjectHook(0x42C760, &CCamera::IsSphereVisible, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/Camera.h b/src/Camera.h
new file mode 100644
index 00000000..46396ec4
--- /dev/null
+++ b/src/Camera.h
@@ -0,0 +1,420 @@
+#pragma once
+
+#include "Placeable.h"
+
+class CEntity;
+class CPed;
+class CAutomobile;
+
+#define NUMBER_OF_VECTORS_FOR_AVERAGE 2
+
+struct CCam
+{
+ enum CamMode
+ {
+ 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_FIRSTPERSONPEDONPC_38,
+ MODE_FIRSTPERSONPEDONPC_39,
+ 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_unknownZOffset;
+ 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 Process_Debug(float *vec, float a, float b, float c);
+ void Process_Kalvin(float*, float, float, float);
+ void GetVectorsReadyForRW(void);
+};
+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,
+};
+
+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;
+
+
+ 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);
+};
+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/Clock.cpp b/src/Clock.cpp
new file mode 100644
index 00000000..dc8e1599
--- /dev/null
+++ b/src/Clock.cpp
@@ -0,0 +1,116 @@
+#include "common.h"
+#include "patcher.h"
+#include "Timer.h"
+#include "Pad.h"
+#include "Clock.h"
+
+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)->NewState.r1){
+ 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){
+ ms_nGameClockMinutes++;
+ ms_nLastClockTick += ms_nMillisecondsPerGameMinute;
+ if(ms_nGameClockMinutes >= 60){
+ ms_nGameClockHours++;
+ ms_nGameClockMinutes = 0;
+ if(ms_nGameClockHours >= 24)
+ ms_nGameClockHours = 0;
+ // TODO: stats days passed
+ }
+ }
+ 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/Clock.h b/src/Clock.h
new file mode 100644
index 00000000..0b6ba304
--- /dev/null
+++ b/src/Clock.h
@@ -0,0 +1,27 @@
+#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 int8 GetHours(void) { return ms_nGameClockHours; }
+ static int8 GetMinutes(void) { return ms_nGameClockMinutes; }
+ static int16 GetSeconds(void) { return ms_nGameClockSeconds; }
+};
diff --git a/src/Collision.cpp b/src/Collision.cpp
new file mode 100644
index 00000000..14b3adcd
--- /dev/null
+++ b/src/Collision.cpp
@@ -0,0 +1,1629 @@
+#include "common.h"
+#include "patcher.h"
+#include "Game.h"
+#include "General.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;
+
+#if 0
+
+void
+CCollision::Init(void)
+{
+ ms_colModelCache.Init(NUMCOLCACHELINKS);
+ ms_collisionInMemory = LEVEL_NONE;
+}
+
+void
+CCollision::Update(void)
+{
+ CVector pos = FindPlayerCoors();
+ eLevelName level = CTheZones::m_CurrLevel;
+ bool changeLevel = false;
+
+ // hardcode a level if there are no zones
+ if(level == LEVEL_NONE){
+ if(CGame::currLevel == LEVEL_INDUSTRIAL &&
+ pos.x < 400.0f){
+ level = LEVEL_COMMERCIAL;
+ changeLevel = true;
+ }else if(CGame::currLevel == LEVEL_SUBURBAN &&
+ pos.x > -450.0f && pos.y < -1400.0f){
+ level = LEVEL_COMMERCIAL;
+ changeLevel = true;
+ }else{
+ if(pos.x > 800.0f){
+ level = LEVEL_INDUSTRIAL;
+ changeLevel = true;
+ }else if(pos.x < -800.0f){
+ level = LEVEL_SUBURBAN;
+ changeLevel = true;
+ }
+ }
+ }
+ if(level != LEVEL_NONE && level != CGame::currLevel){
+ debug("changing level %d -> %d\n", CGame::currLevel, level);
+ CGame::currLevel = level;
+ }
+ if(ms_collisionInMemory != CGame::currLevel)
+ LoadCollisionWhenINeedIt(changeLevel);
+ CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel);
+}
+
+void
+CCollision::LoadCollisionWhenINeedIt(bool changeLevel)
+{
+ eLevelName level;
+ level = LEVEL_NONE;
+ if(!changeLevel){
+ //assert(0 && "unimplemented");
+ }
+
+ if(level != CGame::currLevel || changeLevel){
+ CTimer::Stop();
+ CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL);
+ CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL);
+ CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN);
+ CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL);
+ CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL);
+ CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN);
+ ms_collisionInMemory = CGame::currLevel;
+ CStreaming::RemoveUnusedBigBuildings(CGame::currLevel);
+ CStreaming::RemoveUnusedBuildings(CGame::currLevel);
+ CStreaming::RequestBigBuildings(CGame::currLevel);
+ CStreaming::LoadAllRequestedModels();
+ CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel);
+ CTimer::Update();
+ }
+}
+
+#endif
+
+//
+// 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(CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSurf78)
+{
+ 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(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8)
+ if(TestLineSphere(newline, model.spheres[i]))
+ return true;
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8)
+ if(TestLineBox(newline, model.boxes[i]))
+ return true;
+
+ CalculateTrianglePlanes(&model);
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8)
+ 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 ignoreSurf78)
+{
+ 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(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8)
+ ProcessLineSphere(newline, model.spheres[i], point, coldist);
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8)
+ ProcessLineBox(newline, model.boxes[i], point, coldist);
+
+ CalculateTrianglePlanes(&model);
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8)
+ 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 ignoreSurf78, 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(!ignoreSurf78 || model.spheres[i].surface != 7 && model.spheres[i].surface != 8)
+ ProcessLineSphere(newline, model.spheres[i], point, coldist);
+
+ for(i = 0; i < model.numBoxes; i++)
+ if(!ignoreSurf78 || model.boxes[i].surface != 7 && model.boxes[i].surface != 8)
+ ProcessLineBox(newline, model.boxes[i], point, coldist);
+
+ CalculateTrianglePlanes(&model);
+ TempStoredPoly.valid = false;
+ for(i = 0; i < model.numTriangles; i++)
+ if(!ignoreSurf78 || model.triangles[i].surface != 7 && model.triangles[i].surface != 8)
+ 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);
+
+ 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 > 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?
+
+ 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];
+}
+
+STARTPATCHES
+ InjectHook(0x4B9C30, (CMatrix& (*)(const CMatrix &src, CMatrix &dst))Invert, 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/Collision.h b/src/Collision.h
new file mode 100644
index 00000000..9d23524f
--- /dev/null
+++ b/src/Collision.h
@@ -0,0 +1,152 @@
+#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);
+};
+
+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(); }
+};
+
+class CCollision
+{
+public:
+ static eLevelName &ms_collisionInMemory;
+ static CLinkList<CColModel*> &ms_colModelCache;
+
+ static void Init(void);
+ static void Update(void);
+ static void LoadCollisionWhenINeedIt(bool changeLevel);
+ 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(CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSurf78);
+
+ 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 ignoreSurf78);
+ static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSurf78, 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/CullZones.cpp b/src/CullZones.cpp
new file mode 100644
index 00000000..dc30790b
--- /dev/null
+++ b/src/CullZones.cpp
@@ -0,0 +1,323 @@
+#include "common.h"
+#include "patcher.h"
+#include "Building.h"
+#include "Treadable.h"
+#include "Pools.h"
+#include "Timer.h"
+#include "Camera.h"
+#include "World.h"
+#include "CullZones.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::Update(void)
+{
+ bool invisible;
+ CVector v;
+
+ 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(v),
+ &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;
+
+ FindPlayerCoors(coors);
+ 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;
+}
+
+WRAPPER void
+CCullZones::MarkSubwayAsInvisible(bool visible)
+{ EAXJMP(0x525AF0);
+}
+
+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)->m_bZoneCulled = false;
+ bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->m_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)->m_bZoneCulled = false;
+ CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled2 = false;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->m_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)->m_bZoneCulled = true;
+ bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->m_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)->m_bZoneCulled = true;;
+ CPools::GetTreadablePool()->GetSlot(i)->m_bZoneCulled2 = true;;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->m_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)->m_bZoneCulled = true;;
+ bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
+ if(bb != -1)
+ CPools::GetBuildingPool()->GetSlot(bb)->m_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(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/CullZones.h b/src/CullZones.h
new file mode 100644
index 00000000..4641f6ad
--- /dev/null
+++ b/src/CullZones.h
@@ -0,0 +1,93 @@
+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 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/Game.cpp b/src/Game.cpp
new file mode 100644
index 00000000..7f9d86a0
--- /dev/null
+++ b/src/Game.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "Game.h"
+
+int &CGame::currLevel = *(int*)0x941514;
diff --git a/src/Game.h b/src/Game.h
new file mode 100644
index 00000000..83a51fab
--- /dev/null
+++ b/src/Game.h
@@ -0,0 +1,15 @@
+#pragma once
+
+enum eLevelName
+{
+ LEVEL_NONE = 0,
+ LEVEL_INDUSTRIAL,
+ LEVEL_COMMERCIAL,
+ LEVEL_SUBURBAN
+};
+
+class CGame
+{
+public:
+ static int &currLevel;
+};
diff --git a/src/General.h b/src/General.h
new file mode 100644
index 00000000..7aacee39
--- /dev/null
+++ b/src/General.h
@@ -0,0 +1,15 @@
+class CGeneral
+{
+public:
+ static float GetATanOfXY(float x, float y){
+ if(y >= 0.0f) return atan2(x, y);
+ return atan2(x, y) + 2*M_PI;
+ }
+
+ // not too sure about all these...
+ static uint16 GetRandomNumber(void)
+ { return myrand() & 0xFFFF; }
+ // Probably don't want to ever reach high
+ static float GetRandomNumberInRange(float low, float high)
+ { return low + (high - low)*(GetRandomNumber()/65536.0f); }
+};
diff --git a/src/Glass.cpp b/src/Glass.cpp
new file mode 100644
index 00000000..7b02cb6c
--- /dev/null
+++ b/src/Glass.cpp
@@ -0,0 +1,15 @@
+#include "common.h"
+#include "patcher.h"
+#include "Glass.h"
+
+WRAPPER void
+CGlass::WindowRespondsToCollision(CEntity *ent, float amount, CVector speed, CVector point, bool foo)
+{
+ EAXJMP(0x503F10);
+}
+
+WRAPPER void
+CGlass::WindowRespondsToSoftCollision(CEntity *ent, float amount)
+{
+ EAXJMP(0x504630);
+}
diff --git a/src/Glass.h b/src/Glass.h
new file mode 100644
index 00000000..5347467b
--- /dev/null
+++ b/src/Glass.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class CEntity;
+
+class CGlass
+{
+public:
+ static void WindowRespondsToCollision(CEntity *ent, float amount, CVector speed, CVector point, bool foo);
+ static void WindowRespondsToSoftCollision(CEntity *ent, float amount);
+};
diff --git a/src/Lists.cpp b/src/Lists.cpp
new file mode 100644
index 00000000..448a0ff1
--- /dev/null
+++ b/src/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/Lists.h b/src/Lists.h
new file mode 100644
index 00000000..7572e882
--- /dev/null
+++ b/src/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/MenuManager.cpp b/src/MenuManager.cpp
new file mode 100644
index 00000000..0e335546
--- /dev/null
+++ b/src/MenuManager.cpp
@@ -0,0 +1,4 @@
+#include "common.h"
+#include "MenuManager.h"
+
+int &CMenuManager::m_PrefsBrightness = *(int*)0x5F2E50;
diff --git a/src/MenuManager.h b/src/MenuManager.h
new file mode 100644
index 00000000..803e4d8d
--- /dev/null
+++ b/src/MenuManager.h
@@ -0,0 +1,5 @@
+class CMenuManager
+{
+public:
+ static int &m_PrefsBrightness;
+};
diff --git a/src/NodeName.cpp b/src/NodeName.cpp
new file mode 100644
index 00000000..c2ffe048
--- /dev/null
+++ b/src/NodeName.cpp
@@ -0,0 +1,15 @@
+#include "common.h"
+#include "patcher.h"
+#include "NodeName.h"
+
+int &gPluginOffset = *(int*)0x64C610;
+
+#define NODENAMEEXT(o) (RWPLUGINOFFSET(char, o, gPluginOffset))
+
+char*
+GetFrameNodeName(RwFrame *frame)
+{
+ if(gPluginOffset < 0)
+ return nil;
+ return NODENAMEEXT(frame);
+}
diff --git a/src/NodeName.h b/src/NodeName.h
new file mode 100644
index 00000000..a4dcf0cf
--- /dev/null
+++ b/src/NodeName.h
@@ -0,0 +1,3 @@
+#pragma once
+
+char *GetFrameNodeName(RwFrame *frame);
diff --git a/src/Pad.cpp b/src/Pad.cpp
new file mode 100644
index 00000000..ab6bf154
--- /dev/null
+++ b/src/Pad.cpp
@@ -0,0 +1,37 @@
+#include "common.h"
+#include "Pad.h"
+
+CPad *CPad::Pads = (CPad*)0x6F0360;
+
+uint16 *CPad::OldKeyState = (uint16*)0x6F1E70;
+uint16 *CPad::NewKeyState = (uint16*)0x6E60D0;
+uint16 *CPad::TempKeyState = (uint16*)0x774DE8;
+
+CMouseControllerState &CPad::OldMouseControllerState = *(CMouseControllerState*)0x8472A0;
+CMouseControllerState &CPad::NewMouseControllerState = *(CMouseControllerState*)0x8809F0;
+CMouseControllerState &CPad::PCTempMouseControllerState = *(CMouseControllerState*)0x6F1E60;
+
+void
+CControllerState::Clear(void)
+{
+ leftX = 0;
+ leftY = 0;
+ rightX = 0;
+ rightY = 0;
+ l1 = 0;
+ l2 = 0;
+ r1 = 0;
+ r2 = 0;
+ up = 0;
+ down = 0;
+ left = 0;
+ right = 0;
+ start = 0;
+ select = 0;
+ square = 0;
+ triangle = 0;
+ cross = 0;
+ circle = 0;
+ leftshock = 0;
+ rightshock = 0;
+}
diff --git a/src/Pad.h b/src/Pad.h
new file mode 100644
index 00000000..ddcfad8d
--- /dev/null
+++ b/src/Pad.h
@@ -0,0 +1,116 @@
+#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 leftX, leftY;
+ int16 rightX, rightY;
+ int16 l1, l2;
+ int16 r1, r2;
+ int16 up, down, left, right;
+ int16 start, select;
+ int16 square, triangle, cross, circle;
+ int16 leftshock, rightshock;
+ int16 networktalk;
+ float getLeftX(void) { return leftX/32767.0f; };
+ float getLeftY(void) { return leftY/32767.0f; };
+ float getRightX(void) { return rightX/32767.0f; };
+ float getRightY(void) { return rightY/32767.0f; };
+
+ void Clear(void);
+};
+static_assert(sizeof(CControllerState) == 0x2A, "CControllerState: error");
+
+struct CMouseControllerState
+{
+ uint32 btns; // bit 0-2 button 1-3
+ int x, y;
+};
+
+class CPad
+{
+public:
+ CControllerState NewState;
+ CControllerState OldState;
+ CControllerState PCTempKeyState;
+ CControllerState PCTempJoyState;
+ CControllerState PCTempMouseState;
+ // straight out of my IDB
+ int16 Phase;
+ int16 Mode;
+ int16 ShakeDur;
+ int8 ShakeFreq;
+ int8 bHornHistory[5];
+ int8 iCurrHornHistory;
+ int8 DisablePlayerControls;
+ int8 JustOutOfFrontEnd;
+ int8 bApplyBrakes;
+ int32 unk[3];
+ int32 LastTimeTouched;
+ int32 AverageWeapon;
+ int32 AverageEntries;
+
+ static CPad *Pads; //[2];
+ static uint16 *OldKeyState; //[KEY_NUMKEYS];
+ static uint16 *NewKeyState; //[KEY_NUMKEYS];
+ static uint16 *TempKeyState; //[KEY_NUMKEYS];
+ static CMouseControllerState &OldMouseControllerState;
+ static CMouseControllerState &NewMouseControllerState;
+ static CMouseControllerState &PCTempMouseControllerState;
+
+ static CPad *GetPad(int n) { return &Pads[n]; }
+};
+static_assert(sizeof(CPad) == 0xFC, "CPad: error");
+
+#define IsButtonJustDown(pad, btn) \
+ (!(pad)->OldState.btn && (pad)->NewState.btn)
diff --git a/src/ParticleObject.cpp b/src/ParticleObject.cpp
new file mode 100644
index 00000000..fc15c2f7
--- /dev/null
+++ b/src/ParticleObject.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "ParticleObject.h"
+
+void CParticleObject::AddObject(uint16, const CVector &pos, bool remove) { EAXJMP(0x4BC4D0); }
diff --git a/src/ParticleObject.h b/src/ParticleObject.h
new file mode 100644
index 00000000..b2cfadb8
--- /dev/null
+++ b/src/ParticleObject.h
@@ -0,0 +1,31 @@
+#pragma once
+
+enum eParticleObjectType
+{
+ POBJECT_PAVEMENT_STEAM,
+ POBJECT_PAVEMENT_STEAM_SLOWMOTION,
+ POBJECT_WALL_STEAM,
+ POBJECT_WALL_STEAM_SLOWMOTION,
+ POBJECT_DARK_SMOKE,
+ POBJECT_FIRE_HYDRANT,
+ POBJECT_CAR_WATER_SPLASH,
+ POBJECT_PED_WATER_SPLASH,
+ POBJECT_SPLASHES_AROUND,
+ POBJECT_SMALL_FIRE,
+ POBJECT_BIG_FIRE,
+ POBJECT_DRY_ICE,
+ POBJECT_DRY_ICE_SLOWMOTION,
+ POBJECT_FIRE_TRAIL,
+ POBJECT_SMOKE_TRAIL,
+ POBJECT_FIREBALL_AND_SMOKE,
+ POBJECT_ROCKET_TRAIL,
+ POBJECT_EXPLOSION_ONCE,
+ POBJECT_CATALINAS_GUNFLASH,
+ POBJECT_CATALINAS_SHOTGUNFLASH,
+};
+
+class CParticleObject
+{
+public:
+ static void AddObject(uint16, const CVector &pos, bool remove);
+};
diff --git a/src/PathFind.cpp b/src/PathFind.cpp
new file mode 100644
index 00000000..c337ca88
--- /dev/null
+++ b/src/PathFind.cpp
@@ -0,0 +1,591 @@
+#include "common.h"
+#include "patcher.h"
+#include "PathFind.h"
+
+CPathFind &ThePaths = *(CPathFind*)0x8F6754;
+
+int TempListLength;
+
+enum
+{
+ NodeTypeExtern = 1,
+ NodeTypeIntern = 2,
+
+ PathTypeCar = 0,
+ PathTypePed = 1,
+
+ PathNodeFlag1 = 1, // used?
+ PathNodeFlag2 = 2,
+ PathNodeDeadEnd = 4,
+ PathNodeDisabled = 8,
+ PathNodeBetweenLevels = 0x10,
+};
+
+// link flags:
+// 1: crosses road
+// 2: ped traffic light
+// pathnode flags:
+// 1:
+// 2:
+// 4: dead end
+// 8: switched off
+// 10: road between levels??
+// navi node flags:
+// 1: bridge light
+// object flags:
+// 1
+// 2 east/west road(?)
+
+CPathInfoForObject *&InfoForTileCars = *(CPathInfoForObject**)0x8F1A8C;
+CPathInfoForObject *&InfoForTilePeds = *(CPathInfoForObject**)0x8F1AE4;
+// unused
+CTempDetachedNode *&DetachedNodesCars = *(CTempDetachedNode**)0x8E2824;
+CTempDetachedNode *&DetachedNodesPeds = *(CTempDetachedNode**)0x8E28A0;
+
+void
+CPathFind::PreparePathData(void)
+{
+ int i, j, k;
+ int numExtern, numIntern, numLanes;
+ float maxX, maxY;
+ CTempNode *tempNodes;
+
+ printf("PreparePathData\n");
+ // UNUSED: CPathFind::LoadPathFindData
+ if(InfoForTileCars && InfoForTilePeds &&
+ DetachedNodesCars && DetachedNodesPeds){
+ tempNodes = new CTempNode[4000];
+
+ m_numLinks = 0;
+ for(i = 0; i < PATHNODESIZE; i++)
+ m_pathNodes[i].flags &= ~(PathNodeFlag1 | PathNodeFlag2);
+
+ for(i = 0; i < PATHNODESIZE; i++){
+ numExtern = 0;
+ numIntern = 0;
+ for(j = 0; j < 12; j++){
+ if(InfoForTileCars[i*12 + j].type == NodeTypeExtern)
+ numExtern++;
+ if(InfoForTileCars[i*12 + j].type == NodeTypeIntern)
+ numIntern++;
+ }
+ if(numIntern > 1 && numExtern != 2)
+ printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i);
+ }
+
+ for(i = 0; i < PATHNODESIZE; i++)
+ for(j = 0; j < 12; j++)
+ if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){
+ if(InfoForTileCars[i*12 + j].numLeftLanes < 0)
+ printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i);
+ if(InfoForTileCars[i*12 + j].numRightLanes < 0)
+ printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i);
+ if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0)
+ printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i);
+ }
+
+ m_numPathNodes = 0;
+ PreparePathDataForType(PathTypeCar, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, 100);
+ m_numCarPathNodes = m_numPathNodes;
+ PreparePathDataForType(PathTypePed, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, 50);
+ m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes;
+
+ // TODO: figure out what exactly is going on here
+ // Some roads seem to get a west/east flag
+ for(i = 0; i < m_numMapObjects; i++){
+ numExtern = 0;
+ numIntern = 0;
+ numLanes = 0;
+ maxX = 0.0f;
+ maxY = 0.0f;
+ for(j = 0; j < 12; j++){
+ k = i*12 + j;
+ if(InfoForTileCars[k].type == NodeTypeExtern){
+ numExtern++;
+ if(InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes > numLanes)
+ numLanes = InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes;
+ maxX = max(maxX, fabs(InfoForTileCars[k].x));
+ maxY = max(maxY, fabs(InfoForTileCars[k].y));
+ }else if(InfoForTileCars[k].type == NodeTypeIntern)
+ numIntern++;
+ }
+
+ if(numIntern == 1 && numExtern == 2){
+ if(numLanes < 4){
+ if((i & 7) == 4){ // WHAT?
+ m_objectFlags[i] |= 1;
+ if(maxX > maxY)
+ m_objectFlags[i] |= 2;
+ else
+ m_objectFlags[i] &= ~2;
+ }
+ }else{
+ m_objectFlags[i] |= 1;
+ if(maxX > maxY)
+ m_objectFlags[i] |= 2;
+ else
+ m_objectFlags[i] &= ~2;
+ }
+ }
+ }
+
+ delete[] tempNodes;
+
+ CountFloodFillGroups(PathTypeCar);
+ CountFloodFillGroups(PathTypePed);
+
+ delete[] InfoForTileCars;
+ InfoForTileCars = nil;
+ delete[] InfoForTilePeds;
+ InfoForTilePeds = nil;
+ delete[] DetachedNodesCars;
+ DetachedNodesCars = nil;
+ delete[] DetachedNodesPeds;
+ DetachedNodesPeds = nil;
+ }
+ printf("Done with PreparePathData\n");
+}
+
+/* String together connected nodes in a list by a flood fill algorithm */
+void
+CPathFind::CountFloodFillGroups(uint8 type)
+{
+ int start, end;
+ int i, l;
+ uint16 n;
+ CPathNode *node, *prev;
+
+ switch(type){
+ case PathTypeCar:
+ start = 0;
+ end = m_numCarPathNodes;
+ break;
+ case PathTypePed:
+ start = m_numCarPathNodes;
+ end = start + m_numPedPathNodes;
+ break;
+ }
+
+ for(i = start; i < end; i++)
+ m_pathNodes[i].group = 0;
+
+ n = 0;
+ for(;;){
+ n++;
+ if(n > 1500){
+ for(i = start; m_pathNodes[i].group && i < end; i++);
+ printf("NumNodes:%d Accounted for:%d\n", end - start, i - start);
+ }
+
+ // Look for unvisited node
+ for(i = start; m_pathNodes[i].group && i < end; i++);
+ if(i == end)
+ break;
+
+ node = &m_pathNodes[i];
+ node->next = nil;
+ node->group = n;
+
+ if(node->numLinks == 0){
+ if(type == PathTypeCar)
+ printf("Single car node: %f %f %f (%d)\n",
+ node->pos.x, node->pos.y, node->pos.z,
+ m_mapObjects[node->objectIndex]->m_modelIndex);
+ else
+ printf("Single ped node: %f %f %f\n",
+ node->pos.x, node->pos.y, node->pos.z);
+ }
+
+ while(node){
+ prev = node;
+ node = node->next;
+ for(i = 0; i < prev->numLinks; i++){
+ l = m_linkTo[prev->firstLink + i];
+ if(m_pathNodes[l].group == 0){
+ m_pathNodes[l].group = n;
+ if(m_pathNodes[l].group == 0)
+ m_pathNodes[l].group = 0x80; // ???
+ m_pathNodes[l].next = node;
+ node = &m_pathNodes[l];
+ }
+ }
+ }
+ }
+
+ m_numGroups[type] = n-1;
+ printf("GraphType:%d. FloodFill groups:%d\n", type, n);
+}
+
+void
+CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo,
+ float maxdist, CTempDetachedNode *detachednodes, int unused)
+{
+ static CVector CoorsXFormed;
+ int i, j, k, l;
+ int l1, l2;
+ int start, typeoff;
+ float posx, posy;
+ float dx, dy, mag;
+ float nearestDist;
+ int nearestId;
+ int next;
+ int oldNumPathNodes, oldNumLinks;
+ CVector dist;
+ int iseg, jseg;
+ int istart, jstart;
+ int done, cont;
+
+ typeoff = 12*type;
+ oldNumPathNodes = m_numPathNodes;
+ oldNumLinks = m_numLinks;
+
+ // Initialize map objects
+ for(i = 0; i < m_numMapObjects; i++)
+ for(j = 0; j < 12; j++)
+ m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = -1;
+
+ // Calculate internal nodes, store them and connect them to defining object
+ for(i = 0; i < m_numMapObjects; i++){
+ start = 12*m_mapObjects[i]->m_modelIndex;
+ for(j = 0; j < 12; j++){
+ if(objectpathinfo[start + j].type != NodeTypeIntern)
+ continue;
+ CalcNodeCoors(
+ objectpathinfo[start + j].x,
+ objectpathinfo[start + j].y,
+ objectpathinfo[start + j].z,
+ i,
+ &CoorsXFormed);
+ m_pathNodes[m_numPathNodes].pos = CoorsXFormed;
+ m_pathNodes[m_numPathNodes].objectIndex = i;
+ m_pathNodes[m_numPathNodes].flags |= 1;
+ m_mapObjects[i]->m_nodeIndicesCars[typeoff + j] = m_numPathNodes++;
+ }
+ }
+
+ // Insert external nodes into TempList
+ TempListLength = 0;
+ for(i = 0; i < m_numMapObjects; i++){
+ start = 12*m_mapObjects[i]->m_modelIndex;
+
+ for(j = 0; j < 12; j++){
+ if(objectpathinfo[start + j].type != NodeTypeExtern)
+ continue;
+ CalcNodeCoors(
+ objectpathinfo[start + j].x,
+ objectpathinfo[start + j].y,
+ objectpathinfo[start + j].z,
+ i,
+ &CoorsXFormed);
+
+ // find closest unconnected node
+ nearestId = -1;
+ nearestDist = maxdist;
+ for(k = 0; k < TempListLength; k++){
+ if(tempnodes[k].linkState != 1)
+ continue;
+ dx = tempnodes[k].pos.x - CoorsXFormed.x;
+ if(fabs(dx) < nearestDist){
+ dy = tempnodes[k].pos.y - CoorsXFormed.y;
+ if(fabs(dy) < nearestDist){
+ nearestDist = max(fabs(dx), fabs(dy));
+ nearestId = k;
+ }
+ }
+ }
+
+ if(nearestId < 0){
+ // None found, add this one to temp list
+ tempnodes[TempListLength].pos = CoorsXFormed;
+ next = objectpathinfo[start + j].next;
+ if(next < 0){
+ // no link from this node, find link to this node
+ next = 0;
+ for(k = start; j != objectpathinfo[k].next; k++)
+ next++;
+ }
+ // link to connecting internal node
+ tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next];
+ if(type == PathTypeCar){
+ tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes;
+ tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes;
+ }
+ tempnodes[TempListLength++].linkState = 1;
+ }else{
+ // Found nearest, connect it to our neighbour
+ next = objectpathinfo[start + j].next;
+ if(next < 0){
+ // no link from this node, find link to this node
+ next = 0;
+ for(k = start; j != objectpathinfo[k].next; k++)
+ next++;
+ }
+ tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndicesCars[typeoff + next];
+ tempnodes[nearestId].linkState = 2;
+
+ // collapse this node with nearest we found
+ dx = m_pathNodes[tempnodes[nearestId].link1].pos.x - m_pathNodes[tempnodes[nearestId].link2].pos.x;
+ dy = m_pathNodes[tempnodes[nearestId].link1].pos.y - m_pathNodes[tempnodes[nearestId].link2].pos.y;
+ tempnodes[nearestId].pos.x = (tempnodes[nearestId].pos.x + CoorsXFormed.x)*0.5f;
+ tempnodes[nearestId].pos.y = (tempnodes[nearestId].pos.y + CoorsXFormed.y)*0.5f;
+ tempnodes[nearestId].pos.z = (tempnodes[nearestId].pos.z + CoorsXFormed.z)*0.5f;
+ mag = sqrt(dx*dx + dy*dy);
+ tempnodes[nearestId].dirX = dx/mag;
+ tempnodes[nearestId].dirY = dy/mag;
+ // do something when number of lanes doesn't agree
+ if(type == PathTypeCar)
+ if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 &&
+ (objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){
+ tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numLeftLanes;
+ tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numRightLanes;
+ }
+ }
+ }
+ }
+
+ // Loop through previously added internal nodes and link them
+ for(i = oldNumPathNodes; i < m_numPathNodes; i++){
+ // Init link
+ m_pathNodes[i].numLinks = 0;
+ m_pathNodes[i].firstLink = m_numLinks;
+
+ // See if node connects to external nodes
+ for(j = 0; j < TempListLength; j++){
+ if(tempnodes[j].linkState != 2)
+ continue;
+
+ // Add link to other side of the external
+ if(tempnodes[j].link1 == i)
+ m_linkTo[m_numLinks] = tempnodes[j].link2;
+ else if(tempnodes[j].link2 == i)
+ m_linkTo[m_numLinks] = tempnodes[j].link1;
+ else
+ continue;
+
+ dist = m_pathNodes[i].pos - m_pathNodes[m_linkTo[m_numLinks]].pos;
+ m_distTo[m_numLinks] = dist.Magnitude();
+ m_linkFlags[m_numLinks] = 0;
+
+ if(type == PathTypeCar){
+ // IMPROVE: use a goto here
+ // Find existing navi node
+ for(k = 0; k < m_numNaviNodes; k++){
+ if(m_naviNodes[k].dirX == tempnodes[j].dirX &&
+ m_naviNodes[k].dirY == tempnodes[j].dirY &&
+ m_naviNodes[k].posX == tempnodes[j].pos.x &&
+ m_naviNodes[k].posY == tempnodes[j].pos.y){
+ m_naviNodeLinks[m_numLinks] = k;
+ k = m_numNaviNodes;
+ }
+ }
+ // k is m_numNaviNodes+1 if we found one
+ if(k == m_numNaviNodes){
+ m_naviNodes[m_numNaviNodes].dirX = tempnodes[j].dirX;
+ m_naviNodes[m_numNaviNodes].dirY = tempnodes[j].dirY;
+ m_naviNodes[m_numNaviNodes].posX = tempnodes[j].pos.x;
+ m_naviNodes[m_numNaviNodes].posY = tempnodes[j].pos.y;
+ m_naviNodes[m_numNaviNodes].pathNodeIndex = i;
+ m_naviNodes[m_numNaviNodes].numLeftLanes = tempnodes[j].numLeftLanes;
+ m_naviNodes[m_numNaviNodes].numRightLanes = tempnodes[j].numRightLanes;
+ m_naviNodes[m_numNaviNodes].trafficLightType = 0;
+ m_naviNodeLinks[m_numLinks] = m_numNaviNodes++;
+ }
+ }
+
+ m_pathNodes[i].numLinks++;
+ m_numLinks++;
+ }
+
+ // Find i inside path segment
+ iseg = 0;
+ for(j = max(oldNumPathNodes, i-12); j < i; j++)
+ if(m_pathNodes[j].objectIndex == m_pathNodes[i].objectIndex)
+ iseg++;
+
+ istart = 12*m_mapObjects[m_pathNodes[i].objectIndex]->m_modelIndex;
+ // Add links to other internal nodes
+ for(j = max(oldNumPathNodes, i-12); j < min(m_numPathNodes, i+12); j++){
+ if(m_pathNodes[i].objectIndex != m_pathNodes[j].objectIndex || i == j)
+ continue;
+ // N.B.: in every path segment, the externals have to be at the end
+ jseg = j-i + iseg;
+
+ jstart = 12*m_mapObjects[m_pathNodes[j].objectIndex]->m_modelIndex;
+ if(objectpathinfo[istart + iseg].next == jseg ||
+ objectpathinfo[jstart + jseg].next == iseg){
+ // Found a link between i and j
+ m_linkTo[m_numLinks] = j;
+ dist = m_pathNodes[i].pos - m_pathNodes[j].pos;
+ m_distTo[m_numLinks] = dist.Magnitude();
+
+ if(type == PathTypeCar){
+ posx = (m_pathNodes[i].pos.x + m_pathNodes[j].pos.x)*0.5f;
+ posy = (m_pathNodes[i].pos.y + m_pathNodes[j].pos.y)*0.5f;
+ dx = m_pathNodes[j].pos.x - m_pathNodes[i].pos.x;
+ dy = m_pathNodes[j].pos.y - m_pathNodes[i].pos.y;
+ mag = sqrt(dx*dx + dy*dy);
+ dx /= mag;
+ dy /= mag;
+ if(i < j){
+ dx = -dx;
+ dy = -dy;
+ }
+ // IMPROVE: use a goto here
+ // Find existing navi node
+ for(k = 0; k < m_numNaviNodes; k++){
+ if(m_naviNodes[k].dirX == dx &&
+ m_naviNodes[k].dirY == dy &&
+ m_naviNodes[k].posX == posx &&
+ m_naviNodes[k].posY == posy){
+ m_naviNodeLinks[m_numLinks] = k;
+ k = m_numNaviNodes;
+ }
+ }
+ // k is m_numNaviNodes+1 if we found one
+ if(k == m_numNaviNodes){
+ m_naviNodes[m_numNaviNodes].dirX = dx;
+ m_naviNodes[m_numNaviNodes].dirY = dy;
+ m_naviNodes[m_numNaviNodes].posX = posx;
+ m_naviNodes[m_numNaviNodes].posY = posy;
+ m_naviNodes[m_numNaviNodes].pathNodeIndex = i;
+ m_naviNodes[m_numNaviNodes].numLeftLanes = -1;
+ m_naviNodes[m_numNaviNodes].numRightLanes = -1;
+ m_naviNodes[m_numNaviNodes].trafficLightType = 0;
+ m_naviNodeLinks[m_numLinks] = m_numNaviNodes++;
+ }
+ }else{
+ // Crosses road
+ if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].flag & 1 ||
+ objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].flag & 1)
+ m_linkFlags[m_numLinks] |= 1;
+ else
+ m_linkFlags[m_numLinks] &= ~1;
+ }
+
+ m_pathNodes[i].numLinks++;
+ m_numLinks++;
+ }
+ }
+ }
+
+ if(type == PathTypeCar){
+ done = 0;
+ // Set number of lanes for all nodes somehow
+ // very strange code
+ for(k = 0; !done && k < 10; k++){
+ done = 1;
+ for(i = 0; i < m_numPathNodes; i++){
+ if(m_pathNodes[i].numLinks != 2)
+ continue;
+ l1 = m_naviNodeLinks[m_pathNodes[i].firstLink];
+ l2 = m_naviNodeLinks[m_pathNodes[i].firstLink+1];
+
+ if(m_naviNodes[l1].numLeftLanes == -1 &&
+ m_naviNodes[l2].numLeftLanes != -1){
+ done = 0;
+ if(m_naviNodes[l2].pathNodeIndex == i){
+ // why switch left and right here?
+ m_naviNodes[l1].numLeftLanes = m_naviNodes[l2].numRightLanes;
+ m_naviNodes[l1].numRightLanes = m_naviNodes[l2].numLeftLanes;
+ }else{
+ m_naviNodes[l1].numLeftLanes = m_naviNodes[l2].numLeftLanes;
+ m_naviNodes[l1].numRightLanes = m_naviNodes[l2].numRightLanes;
+ }
+ m_naviNodes[l1].pathNodeIndex = i;
+ }else if(m_naviNodes[l1].numLeftLanes != -1 &&
+ m_naviNodes[l2].numLeftLanes == -1){
+ done = 0;
+ if(m_naviNodes[l1].pathNodeIndex == i){
+ // why switch left and right here?
+ m_naviNodes[l2].numLeftLanes = m_naviNodes[l1].numRightLanes;
+ m_naviNodes[l2].numRightLanes = m_naviNodes[l1].numLeftLanes;
+ }else{
+ m_naviNodes[l2].numLeftLanes = m_naviNodes[l1].numLeftLanes;
+ m_naviNodes[l2].numRightLanes = m_naviNodes[l1].numRightLanes;
+ }
+ m_naviNodes[l2].pathNodeIndex = i;
+ }else if(m_naviNodes[l1].numLeftLanes == -1 &&
+ m_naviNodes[l2].numLeftLanes == -1)
+ done = 0;
+ }
+ }
+
+ // Fall back to default values for number of lanes
+ for(i = 0; i < m_numPathNodes; i++)
+ for(j = 0; j < m_pathNodes[i].numLinks; j++){
+ k = m_naviNodeLinks[m_pathNodes[i].firstLink + j];
+ if(m_naviNodes[k].numLeftLanes < 0)
+ m_naviNodes[k].numLeftLanes = 1;
+ if(m_naviNodes[k].numRightLanes < 0)
+ m_naviNodes[k].numRightLanes = 1;
+ }
+ }
+
+ // Set flags for car nodes
+ if(type == PathTypeCar){
+ do{
+ cont = 0;
+ for(i = 0; i < m_numPathNodes; i++){
+ m_pathNodes[i].flags &= ~PathNodeDisabled;
+ m_pathNodes[i].flags &= ~PathNodeBetweenLevels;
+ // See if node is a dead end, if so, we're not done yet
+ if((m_pathNodes[i].flags & PathNodeDeadEnd) == 0){
+ k = 0;
+ for(j = 0; j < m_pathNodes[i].numLinks; j++)
+ if((m_pathNodes[m_linkTo[m_pathNodes[i].firstLink + j]].flags & PathNodeDeadEnd) == 0)
+ k++;
+ if(k < 2){
+ m_pathNodes[i].flags |= PathNodeDeadEnd;
+ cont = 1;
+ }
+ }
+ }
+ }while(cont);
+ }
+
+ // Remove isolated ped nodes
+ if(type == PathTypePed)
+ for(i = oldNumPathNodes; i < m_numPathNodes; i++){
+ if(m_pathNodes[i].numLinks != 0)
+ continue;
+
+ // Remove node
+ for(j = i; j < m_numPathNodes-1; j++)
+ m_pathNodes[j] = m_pathNodes[j+1];
+
+ // Fix links
+ for(j = oldNumLinks; j < m_numLinks; j++)
+ if(m_linkTo[j] >= i)
+ m_linkTo[j]--;
+
+ // Also in treadables
+ for(j = 0; j < m_numMapObjects; j++)
+ for(k = 0; k < 12; k++){
+ if(m_mapObjects[j]->m_nodeIndicesPeds[k] == i){
+ // remove this one
+ for(l = k; l < 12-1; l++)
+ m_mapObjects[j]->m_nodeIndicesPeds[l] = m_mapObjects[j]->m_nodeIndicesPeds[l+1];
+ m_mapObjects[j]->m_nodeIndicesPeds[11] = -1;
+ }else if(m_mapObjects[j]->m_nodeIndicesPeds[k] > i)
+ m_mapObjects[j]->m_nodeIndicesPeds[k]--;
+ }
+
+ i--;
+ m_numPathNodes--;
+ }
+}
+
+void
+CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out)
+{
+ CVector pos;
+ pos.x = x / 16.0f;
+ pos.y = y / 16.0f;
+ pos.z = z / 16.0f;
+ *out = m_mapObjects[id]->GetMatrix() * pos;
+}
+
+STARTPATCHES
+ InjectHook(0x429610, &CPathFind::PreparePathData, PATCH_JUMP);
+ InjectHook(0x429C20, &CPathFind::PreparePathDataForType, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/PathFind.h b/src/PathFind.h
new file mode 100644
index 00000000..f4857ce2
--- /dev/null
+++ b/src/PathFind.h
@@ -0,0 +1,130 @@
+#pragma once
+
+#include "Treadable.h"
+
+struct CPathNode
+{
+ CVector pos;
+ CPathNode *prev; //?
+ CPathNode *next;
+ int16 unknown;
+ int16 objectIndex;
+ int16 firstLink;
+ uint8 numLinks;
+ uint8 flags;
+ uint8 group;
+/* VC:
+ int16 unk1;
+ int16 nextIndex;
+ int16 x;
+ int16 y;
+ int16 z;
+ int16 unknown;
+ int16 firstLink;
+ int8 width;
+ int8 group;
+ int8 numLinks : 4;
+ int8 bDeadEnd : 1;
+ int8 bTurnedOff : 1; // flag 8 in node info
+ int8 flagA40 : 1; // flag 20 in node info
+ int8 flagA80 : 1; // flag 4 in node info
+ int8 flagB1 : 1; // flag 10 in node info
+ int8 flagB2 : 1; // flag 2 in node info
+ int8 flagB4 : 1;
+ int8 speedLimit : 2; // speed limit
+ int8 flagB20 : 1;
+ int8 flagB40 : 1;
+ int8 flagB80 : 1;
+ int8 spawnRate : 4;
+ int8 flagsC : 4;
+*/
+};
+
+// TODO: name?
+struct NaviNode
+{
+ float posX;
+ float posY;
+ float dirX;
+ float dirY;
+ int16 pathNodeIndex;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ int8 trafficLightType;
+ // probably only padding
+ int8 field15;
+ int8 field16;
+ int8 field17;
+};
+
+struct CPathInfoForObject
+{
+ int16 x;
+ int16 y;
+ int16 z;
+ int8 type;
+ int8 next;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ uint8 flag;
+};
+
+struct CTempNode
+{
+ CVector pos;
+ float dirX;
+ float dirY;
+ int16 link1;
+ int16 link2;
+ int8 numLeftLanes;
+ int8 numRightLanes;
+ int8 linkState;
+ // probably padding
+ int8 field1B;
+};
+
+struct CTempDetachedNode // unused
+{
+ uint8 foo[20];
+};
+
+class CPathFind
+{
+public:
+/* For reference VC:
+ CPathNode pathNodes[9650];
+ NaviNode naviNodes[3500];
+ CBuilding *mapObjects[1250];
+ // 0x8000 is cross road flag
+ // 0x4000 is traffic light flag
+ uint16 linkTo[20400];
+ uint8 distTo[20400];
+ int16 naviNodeLinks[20400];
+*/
+ CPathNode m_pathNodes[4930];
+ NaviNode m_naviNodes[2076];
+ CTreadable *m_mapObjects[1250];
+ uint8 m_objectFlags[1250];
+ int16 m_linkTo[10260];
+ int16 m_distTo[10260];
+ uint8 m_linkFlags[10260];
+ int16 m_naviNodeLinks[10260];
+ int32 m_numPathNodes;
+ int32 m_numCarPathNodes;
+ int32 m_numPedPathNodes;
+ int16 m_numMapObjects;
+ int16 m_numLinks;
+ int32 m_numNaviNodes;
+ int32 h;
+ uint8 m_numGroups[2];
+ CPathNode m_aExtraPaths[872];
+
+ void PreparePathData(void);
+ void CountFloodFillGroups(uint8 type);
+ void PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo,
+ float unk, CTempDetachedNode *detachednodes, int unused);
+ void CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out);
+};
+static_assert(sizeof(CPathFind) == 0x4c8f4, "CPathFind: error");
+
+extern CPathFind &ThePaths;
diff --git a/src/Placeable.cpp b/src/Placeable.cpp
new file mode 100644
index 00000000..43708d3e
--- /dev/null
+++ b/src/Placeable.cpp
@@ -0,0 +1,81 @@
+#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 x, xmin, xmax;
+ float y, ymin, ymax;
+ xmin = x1;
+ xmax = x2;
+ ymin = y1;
+ ymax = y2;
+ if(x2 > x1){
+ xmin = x2;
+ xmax = x1;
+ }
+ if(y2 > y1){
+ ymin = y2;
+ ymax = y1;
+ }
+ x = GetPosition().x;
+ y = GetPosition().y;
+ return xmin <= x && x <= xmax &&
+ ymin <= y && y <= ymax;
+}
+
+bool
+CPlaceable::IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2)
+{
+ float x, xmin, xmax;
+ float y, ymin, ymax;
+ float z, zmin, zmax;
+ xmin = x1;
+ xmax = x2;
+ ymin = y1;
+ ymax = y2;
+ zmin = z1;
+ zmax = z2;
+ if(x2 > x1){
+ xmin = x2;
+ xmax = x1;
+ }
+ if(y2 > y1){
+ ymin = y2;
+ ymax = y1;
+ }
+ if(z2 > z1){
+ zmin = z2;
+ zmax = z1;
+ }
+ x = GetPosition().x;
+ y = GetPosition().y;
+ z = GetPosition().z;
+ return xmin <= x && x <= xmax &&
+ ymin <= y && y <= ymax &&
+ zmin <= z && z <= zmax;
+}
+
+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/Placeable.h b/src/Placeable.h
new file mode 100644
index 00000000..bca9462d
--- /dev/null
+++ b/src/Placeable.h
@@ -0,0 +1,26 @@
+#pragma once
+
+class CPlaceable
+{
+ // disable allocation
+ static void *operator new(size_t) { assert(0); return nil; }
+ static void operator delete(void*, size_t) { assert(0); }
+public:
+ 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/Pools.cpp b/src/Pools.cpp
new file mode 100644
index 00000000..106715e4
--- /dev/null
+++ b/src/Pools.cpp
@@ -0,0 +1,19 @@
+#include "common.h"
+#include "Pools.h"
+
+CCPtrNodePool *&CPools::ms_pPtrNodePool = *(CCPtrNodePool**)0x943044;
+CEntryInfoNodePool *&CPools::ms_pEntryInfoNodePool = *(CEntryInfoNodePool**)0x941448;
+CBuildingPool *&CPools::ms_pBuildingPool = *(CBuildingPool**)0x8F2C04;
+CTreadablePool *&CPools::ms_pTreadablePool = *(CTreadablePool**)0x8F2568;
+CObjectPool *&CPools::ms_pObjectPool = *(CObjectPool**)0x880E28;
+
+void
+CPools::Initialise(void)
+{
+ // TODO: unused right now
+ ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES);
+ ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS);
+ ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS);
+ ms_pTreadablePool = new CTreadablePool(NUMTREADABLES);
+ ms_pObjectPool = new CObjectPool(NUMOBJECTS);
+}
diff --git a/src/Pools.h b/src/Pools.h
new file mode 100644
index 00000000..aa804788
--- /dev/null
+++ b/src/Pools.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "templates.h"
+#include "Lists.h"
+#include "Treadable.h"
+#include "Object.h"
+#include "CutsceneHead.h"
+
+typedef CPool<CPtrNode> CCPtrNodePool;
+typedef CPool<CEntryInfoNode> CEntryInfoNodePool;
+typedef CPool<CBuilding> CBuildingPool;
+typedef CPool<CTreadable> CTreadablePool;
+typedef CPool<CObject, CCutsceneHead> CObjectPool;
+
+class CPools
+{
+ static CCPtrNodePool *&ms_pPtrNodePool;
+ static CEntryInfoNodePool *&ms_pEntryInfoNodePool;
+ // ms_pPedPool
+ // ms_pVehiclePool
+ static CBuildingPool *&ms_pBuildingPool;
+ static CTreadablePool *&ms_pTreadablePool;
+ static CObjectPool *&ms_pObjectPool;
+ // ms_pDummyPool
+ // ms_pAudioScriptObjectPool
+public:
+ static CCPtrNodePool *GetPtrNodePool(void) { return ms_pPtrNodePool; }
+ static CEntryInfoNodePool *GetEntryInfoNodePool(void) { return ms_pEntryInfoNodePool; }
+ static CBuildingPool *GetBuildingPool(void) { return ms_pBuildingPool; }
+ static CTreadablePool *GetTreadablePool(void) { return ms_pTreadablePool; }
+ static CObjectPool *GetObjectPool(void) { return ms_pObjectPool; }
+
+ static void Initialise(void);
+};
diff --git a/src/References.cpp b/src/References.cpp
new file mode 100644
index 00000000..187f4ffa
--- /dev/null
+++ b/src/References.cpp
@@ -0,0 +1,22 @@
+#include "common.h"
+#include "patcher.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;
+}
+
+STARTPATCHES
+ InjectHook(0x4A7350, CReferences::Init, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/References.h b/src/References.h
new file mode 100644
index 00000000..5ee20d38
--- /dev/null
+++ b/src/References.h
@@ -0,0 +1,17 @@
+#pragma once
+
+class CEntity;
+
+struct CReference
+{
+ CReference *next;
+ CEntity **pentity;
+};
+
+class CReferences
+{
+ static CReference *aRefs; //[NUMREFERENCES];
+public:
+ static CReference *&pEmptyList;
+ static void Init(void);
+};
diff --git a/src/RwHelper.cpp b/src/RwHelper.cpp
new file mode 100644
index 00000000..c8782f9e
--- /dev/null
+++ b/src/RwHelper.cpp
@@ -0,0 +1,19 @@
+#include "common.h"
+
+
+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;
+}
diff --git a/src/RwHelper.h b/src/RwHelper.h
new file mode 100644
index 00000000..90852b08
--- /dev/null
+++ b/src/RwHelper.h
@@ -0,0 +1,3 @@
+#pragma once
+
+RwObject *GetFirstObject(RwFrame *frame);
diff --git a/src/Streaming.cpp b/src/Streaming.cpp
new file mode 100644
index 00000000..47258c9d
--- /dev/null
+++ b/src/Streaming.cpp
@@ -0,0 +1,10 @@
+#include "common.h"
+#include "patcher.h"
+#include "Streaming.h"
+
+bool &CStreaming::ms_disableStreaming = *(bool*)0x95CD6E;
+int32 &CStreaming::ms_numModelsRequested = *(int32*)0x8E2C10;
+CStreamingInfo *CStreaming::ms_aInfoForModel = (CStreamingInfo*)0x6C7088;
+
+WRAPPER void CStreaming::RemoveModel(int32 id) { EAXJMP(0x408830); }
+WRAPPER void CStreaming::RequestModel(int32 model, int32 flags) { EAXJMP(0x407EA0); }
diff --git a/src/Streaming.h b/src/Streaming.h
new file mode 100644
index 00000000..1acfc261
--- /dev/null
+++ b/src/Streaming.h
@@ -0,0 +1,54 @@
+#pragma once
+
+enum {
+ STREAM_OFFSET_MODEL = 0,
+ STREAM_OFFSET_TXD = STREAM_OFFSET_MODEL+MODELINFOSIZE,
+ NUMSTREAMINFO = STREAM_OFFSET_TXD+TXDSTORESIZE
+};
+
+enum StreamFlags
+{
+ STREAM_DONT_REMOVE = 0x01,
+ STREAM_SCRIPTOWNED = 0x02,
+ STREAM_DEPENDENCY = 0x04,
+ STREAM_PRIORITY = 0x08,
+ STREAM_NOFADE = 0x10,
+};
+
+enum StreamLoadState
+{
+ STREAM_NOTLOADED = 0,
+ STREAM_LOADED = 1,
+ STREAM_INQUEUE = 2,
+ STREAM_READING = 3, // what is this?
+ STREAM_BIGFILE = 4,
+};
+
+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 *pos, uint32 *size);
+// void SetCdPosnAndSize(uint32 pos, uint32 size);
+// void AddToList(CStreamingInfo *link);
+// void RemoveFromList(void);
+};
+
+class CStreaming
+{
+public:
+ static bool &ms_disableStreaming;
+ static int32 &ms_numModelsRequested;
+ static CStreamingInfo *ms_aInfoForModel; //[NUMSTREAMINFO]
+
+ static void RemoveModel(int32 id);
+ static void RequestModel(int32 model, int32 flags);
+};
diff --git a/src/SurfaceTable.cpp b/src/SurfaceTable.cpp
new file mode 100644
index 00000000..e9ef459a
--- /dev/null
+++ b/src/SurfaceTable.cpp
@@ -0,0 +1,44 @@
+#include "common.h"
+#include "patcher.h"
+#include "SurfaceTable.h"
+
+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;
+ }
+}
diff --git a/src/SurfaceTable.h b/src/SurfaceTable.h
new file mode 100644
index 00000000..556a6e04
--- /dev/null
+++ b/src/SurfaceTable.h
@@ -0,0 +1,99 @@
+#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_ROAD0,
+ SURFACE_ROAD1,
+ SURFACE_GRASS,
+ SURFACE_DIRT,
+ SURFACE_MUD,
+ SURFACE_PAVEMENT,
+ SURFACE_METAL6,
+ SURFACE_GLASS,
+ SURFACE_HARD8,
+ SURFACE_METAL_DOOR,
+ SURFACE_METAL10,
+ SURFACE_METAL11,
+ SURFACE_METAL12,
+ SURFACE_METAL13,
+ SURFACE_METAL14,
+ SURFACE_METAL15,
+ SURFACE_METAL_FENCE,
+ SURFACE_FLESH,
+ SURFACE_SAND18,
+ SURFACE_WATER,
+ 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
+};
+
+class CSurfaceTable
+{
+public:
+ static int GetAdhesionGroup(uint8 surfaceType);
+};
diff --git a/src/Timecycle.cpp b/src/Timecycle.cpp
new file mode 100644
index 00000000..10fd6bf6
--- /dev/null
+++ b/src/Timecycle.cpp
@@ -0,0 +1,92 @@
+#include "common.h"
+#include "Timecycle.h"
+
+int (*CTimeCycle::m_nAmbientRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x86AF78;
+int (*CTimeCycle::m_nAmbientGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x665308;
+int (*CTimeCycle::m_nAmbientBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x72CF88;
+int (*CTimeCycle::m_nDirectionalRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6FAB78;
+int (*CTimeCycle::m_nDirectionalGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6F4528;
+int (*CTimeCycle::m_nDirectionalBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x83CE58;
+int (*CTimeCycle::m_nSkyTopRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87FB90;
+int (*CTimeCycle::m_nSkyTopGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x8460A8;
+int (*CTimeCycle::m_nSkyTopBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87B158;
+int (*CTimeCycle::m_nSkyBottomRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6FA960;
+int (*CTimeCycle::m_nSkyBottomGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x70D6A8;
+int (*CTimeCycle::m_nSkyBottomBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x83D288;
+int (*CTimeCycle::m_nSunCoreRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x878360;
+int (*CTimeCycle::m_nSunCoreGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6EE088;
+int (*CTimeCycle::m_nSunCoreBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x773A68;
+int (*CTimeCycle::m_nSunCoronaRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x664B60;
+int (*CTimeCycle::m_nSunCoronaGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6F01E0;
+int (*CTimeCycle::m_nSunCoronaBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6E6340;
+float (*CTimeCycle::m_fSunSize)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x733510;
+float (*CTimeCycle::m_fSpriteSize)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x87F820;
+float (*CTimeCycle::m_fSpriteBrightness)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x6E96F0;
+short (*CTimeCycle::m_nShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x83CFD8;
+short (*CTimeCycle::m_nLightShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x72B0F8;
+short (*CTimeCycle::m_nTreeShadowStrength)[NUMWEATHERS] = (short(*)[NUMWEATHERS])0x733450;
+float (*CTimeCycle::m_fFogStart)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8806C8;
+float (*CTimeCycle::m_fFarClip)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8804E0;
+float (*CTimeCycle::m_fLightsOnGroundBrightness)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x83D108;
+int (*CTimeCycle::m_nLowCloudsRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x726770;
+int (*CTimeCycle::m_nLowCloudsGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87BF08;
+int (*CTimeCycle::m_nLowCloudsBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x87FA10;
+int (*CTimeCycle::m_nFluffyCloudsTopRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x70F2B0;
+int (*CTimeCycle::m_nFluffyCloudsTopGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x72D288;
+int (*CTimeCycle::m_nFluffyCloudsTopBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x86B108;
+int (*CTimeCycle::m_nFluffyCloudsBottomRed)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6E8DA8;
+int (*CTimeCycle::m_nFluffyCloudsBottomGreen)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x715AA8;
+int (*CTimeCycle::m_nFluffyCloudsBottomBlue)[NUMWEATHERS] = (int(*)[NUMWEATHERS])0x6EE2D0;
+float (*CTimeCycle::m_fBlurRed)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x87C7E0;
+float (*CTimeCycle::m_fBlurGreen)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x774C10;
+float (*CTimeCycle::m_fBlurBlue)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x8784E0;
+float (*CTimeCycle::m_fBlurAlpha)[NUMWEATHERS] = (float(*)[NUMWEATHERS])0x733690;
+
+float &CTimeCycle::m_fCurrentAmbientRed = *(float*)0x8F29B4;
+float &CTimeCycle::m_fCurrentAmbientGreen = *(float*)0x94144C;
+float &CTimeCycle::m_fCurrentAmbientBlue = *(float*)0x942FC0;
+float &CTimeCycle::m_fCurrentDirectionalRed = *(float*)0x8F29D8;
+float &CTimeCycle::m_fCurrentDirectionalGreen = *(float*)0x940594;
+float &CTimeCycle::m_fCurrentDirectionalBlue = *(float*)0x942FAC;
+int &CTimeCycle::m_nCurrentSkyTopRed = *(int*)0x9403C0;
+int &CTimeCycle::m_nCurrentSkyTopGreen = *(int*)0x943074;
+int &CTimeCycle::m_nCurrentSkyTopBlue = *(int*)0x8F29B8;
+int &CTimeCycle::m_nCurrentSkyBottomRed = *(int*)0x9414D0;
+int &CTimeCycle::m_nCurrentSkyBottomGreen = *(int*)0x8F2BD0;
+int &CTimeCycle::m_nCurrentSkyBottomBlue = *(int*)0x8F625C;
+int &CTimeCycle::m_nCurrentSunCoreRed = *(int*)0x8F2534;
+int &CTimeCycle::m_nCurrentSunCoreGreen = *(int*)0x8F6264;
+int &CTimeCycle::m_nCurrentSunCoreBlue = *(int*)0x94149C;
+int &CTimeCycle::m_nCurrentSunCoronaRed = *(int*)0x8F2C1C;
+int &CTimeCycle::m_nCurrentSunCoronaGreen = *(int*)0x885B54;
+int &CTimeCycle::m_nCurrentSunCoronaBlue = *(int*)0x880F60;
+float &CTimeCycle::m_fCurrentSunSize = *(float*)0x940588;
+float &CTimeCycle::m_fCurrentSpriteSize = *(float*)0x8F1AA8;
+float &CTimeCycle::m_fCurrentSpriteBrightness = *(float*)0x8F5FDC;
+int &CTimeCycle::m_nCurrentShadowStrength = *(int*)0x95CC76;
+int &CTimeCycle::m_nCurrentLightShadowStrength = *(int*)0x95CC66;
+int &CTimeCycle::m_nCurrentTreeShadowStrength = *(int*)0x95CC86;
+float &CTimeCycle::m_fCurrentFogStart = *(float*)0x8F1AE0;
+float &CTimeCycle::m_fCurrentFarClip = *(float*)0x8F5FD8;
+float &CTimeCycle::m_fCurrentLightsOnGroundBrightness = *(float*)0x8F1B60;
+int &CTimeCycle::m_nCurrentLowCloudsRed = *(int*)0x95CB54;
+int &CTimeCycle::m_nCurrentLowCloudsGreen = *(int*)0x95CB48;
+int &CTimeCycle::m_nCurrentLowCloudsBlue = *(int*)0x95CC1C;
+int &CTimeCycle::m_nCurrentFluffyCloudsTopRed = *(int*)0x8F2550;
+int &CTimeCycle::m_nCurrentFluffyCloudsTopGreen = *(int*)0x8F59CC;
+int &CTimeCycle::m_nCurrentFluffyCloudsTopBlue = *(int*)0x941434;
+int &CTimeCycle::m_nCurrentFluffyCloudsBottomRed = *(int*)0x8F1A38;
+int &CTimeCycle::m_nCurrentFluffyCloudsBottomGreen = *(int*)0x8E28B8;
+int &CTimeCycle::m_nCurrentFluffyCloudsBottomBlue = *(int*)0x8F3960;
+float &CTimeCycle::m_fCurrentBlurRed = *(float*)0x8F6000;
+float &CTimeCycle::m_fCurrentBlurGreen = *(float*)0x9405A0;
+float &CTimeCycle::m_fCurrentBlurBlue = *(float*)0x8F250C;
+float &CTimeCycle::m_fCurrentBlurAlpha = *(float*)0x940728;
+int &CTimeCycle::m_nCurrentFogColourRed = *(int*)0x940714;
+int &CTimeCycle::m_nCurrentFogColourGreen = *(int*)0x8E2A60;
+int &CTimeCycle::m_nCurrentFogColourBlue = *(int*)0x8F57EC;
+
+int &CTimeCycle::m_FogReduction = *(int*)0x880FB8;
+
+int &CTimeCycle::m_CurrentStoredValue = *(int*)0x94057C;
+CVector *CTimeCycle::m_VectorToSun = (CVector*)0x665548; // [16]
diff --git a/src/Timecycle.h b/src/Timecycle.h
new file mode 100644
index 00000000..6946cc7c
--- /dev/null
+++ b/src/Timecycle.h
@@ -0,0 +1,111 @@
+class CTimeCycle
+{
+ static int (*m_nAmbientRed)[NUMWEATHERS];
+ static int (*m_nAmbientGreen)[NUMWEATHERS];
+ static int (*m_nAmbientBlue)[NUMWEATHERS];
+
+ static int (*m_nDirectionalRed)[NUMWEATHERS];
+ static int (*m_nDirectionalGreen)[NUMWEATHERS];
+ static int (*m_nDirectionalBlue)[NUMWEATHERS];
+ static int (*m_nSkyTopRed)[NUMWEATHERS];
+ static int (*m_nSkyTopGreen)[NUMWEATHERS];
+ static int (*m_nSkyTopBlue)[NUMWEATHERS];
+ static int (*m_nSkyBottomRed)[NUMWEATHERS];
+ static int (*m_nSkyBottomGreen)[NUMWEATHERS];
+ static int (*m_nSkyBottomBlue)[NUMWEATHERS];
+ static int (*m_nSunCoreRed)[NUMWEATHERS];
+ static int (*m_nSunCoreGreen)[NUMWEATHERS];
+ static int (*m_nSunCoreBlue)[NUMWEATHERS];
+ static int (*m_nSunCoronaRed)[NUMWEATHERS];
+ static int (*m_nSunCoronaGreen)[NUMWEATHERS];
+ static int (*m_nSunCoronaBlue)[NUMWEATHERS];
+ static float (*m_fSunSize)[NUMWEATHERS];
+ static float (*m_fSpriteSize)[NUMWEATHERS];
+ static float (*m_fSpriteBrightness)[NUMWEATHERS];
+ static short (*m_nShadowStrength)[NUMWEATHERS];
+ static short (*m_nLightShadowStrength)[NUMWEATHERS];
+ static short (*m_nTreeShadowStrength)[NUMWEATHERS];
+ static float (*m_fFogStart)[NUMWEATHERS];
+ static float (*m_fFarClip)[NUMWEATHERS];
+ static float (*m_fLightsOnGroundBrightness)[NUMWEATHERS];
+ static int (*m_nLowCloudsRed)[NUMWEATHERS];
+ static int (*m_nLowCloudsGreen)[NUMWEATHERS];
+ static int (*m_nLowCloudsBlue)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsTopRed)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsTopGreen)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsTopBlue)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsBottomRed)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsBottomGreen)[NUMWEATHERS];
+ static int (*m_nFluffyCloudsBottomBlue)[NUMWEATHERS];
+ static float (*m_fBlurRed)[NUMWEATHERS];
+ static float (*m_fBlurGreen)[NUMWEATHERS];
+ static float (*m_fBlurBlue)[NUMWEATHERS];
+ static float (*m_fBlurAlpha)[NUMWEATHERS];
+
+ static float &m_fCurrentAmbientRed;
+ static float &m_fCurrentAmbientGreen;
+ static float &m_fCurrentAmbientBlue;
+ static float &m_fCurrentDirectionalRed;
+ static float &m_fCurrentDirectionalGreen;
+ static float &m_fCurrentDirectionalBlue;
+ static int &m_nCurrentSkyTopRed;
+ static int &m_nCurrentSkyTopGreen;
+ static int &m_nCurrentSkyTopBlue;
+ static int &m_nCurrentSkyBottomRed;
+ static int &m_nCurrentSkyBottomGreen;
+ static int &m_nCurrentSkyBottomBlue;
+ static int &m_nCurrentSunCoreRed;
+ static int &m_nCurrentSunCoreGreen;
+ static int &m_nCurrentSunCoreBlue;
+ static int &m_nCurrentSunCoronaRed;
+ static int &m_nCurrentSunCoronaGreen;
+ static int &m_nCurrentSunCoronaBlue;
+ static float &m_fCurrentSunSize;
+ static float &m_fCurrentSpriteSize;
+ static float &m_fCurrentSpriteBrightness;
+ static int &m_nCurrentShadowStrength;
+ static int &m_nCurrentLightShadowStrength;
+ static int &m_nCurrentTreeShadowStrength;
+ static float &m_fCurrentFogStart;
+ static float &m_fCurrentFarClip;
+ static float &m_fCurrentLightsOnGroundBrightness;
+ static int &m_nCurrentLowCloudsRed;
+ static int &m_nCurrentLowCloudsGreen;
+ static int &m_nCurrentLowCloudsBlue;
+ static int &m_nCurrentFluffyCloudsTopRed;
+ static int &m_nCurrentFluffyCloudsTopGreen;
+ static int &m_nCurrentFluffyCloudsTopBlue;
+ static int &m_nCurrentFluffyCloudsBottomRed;
+ static int &m_nCurrentFluffyCloudsBottomGreen;
+ static int &m_nCurrentFluffyCloudsBottomBlue;
+ static float &m_fCurrentBlurRed;
+ static float &m_fCurrentBlurGreen;
+ static float &m_fCurrentBlurBlue;
+ static float &m_fCurrentBlurAlpha;
+ static int &m_nCurrentFogColourRed;
+ static int &m_nCurrentFogColourGreen;
+ static int &m_nCurrentFogColourBlue;
+
+ static int &m_FogReduction;
+
+public:
+ static int &m_CurrentStoredValue;
+ static CVector *m_VectorToSun; // [16]
+
+ static float GetAmbientRed(void) { return m_fCurrentAmbientRed; }
+ static float GetAmbientGreen(void) { return m_fCurrentAmbientGreen; }
+ static float GetAmbientBlue(void) { return m_fCurrentAmbientBlue; }
+ static float GetDirectionalRed(void) { return m_fCurrentDirectionalRed; }
+ static float GetDirectionalGreen(void) { return m_fCurrentDirectionalGreen; }
+ static float GetDirectionalBlue(void) { return m_fCurrentDirectionalBlue; }
+ static int GetLowCloudsRed(void) { return m_nCurrentLowCloudsRed; }
+ static int GetLowCloudsGreen(void) { return m_nCurrentLowCloudsGreen; }
+ static int GetLowCloudsBlue(void) { return m_nCurrentLowCloudsBlue; }
+ static int GetFluffyCloudsTopRed(void) { return m_nCurrentFluffyCloudsTopRed; }
+ static int GetFluffyCloudsTopGreen(void) { return m_nCurrentFluffyCloudsTopGreen; }
+ static int GetFluffyCloudsTopBlue(void) { return m_nCurrentFluffyCloudsTopBlue; }
+ static int GetFluffyCloudsBottomRed(void) { return m_nCurrentFluffyCloudsBottomRed; }
+ static int GetFluffyCloudsBottomGreen(void) { return m_nCurrentFluffyCloudsBottomGreen; }
+ static int GetFluffyCloudsBottomBlue(void) { return m_nCurrentFluffyCloudsBottomBlue; }
+
+};
diff --git a/src/Timer.cpp b/src/Timer.cpp
new file mode 100644
index 00000000..9cc35e42
--- /dev/null
+++ b/src/Timer.cpp
@@ -0,0 +1,14 @@
+#include "common.h"
+#include "patcher.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;
diff --git a/src/Timer.h b/src/Timer.h
new file mode 100644
index 00000000..a96574ce
--- /dev/null
+++ b/src/Timer.h
@@ -0,0 +1,19 @@
+#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 uint32 GetFrameCounter(void) { return m_FrameCounter; }
+ static uint32 GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; }
+};
diff --git a/src/TxdStore.cpp b/src/TxdStore.cpp
new file mode 100644
index 00000000..ba8eecb9
--- /dev/null
+++ b/src/TxdStore.cpp
@@ -0,0 +1,158 @@
+#include "common.h"
+#include "patcher.h"
+#include "templates.h"
+#include "Streaming.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;
+}
+
+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);
+}
+
+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();
+}
+
+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(!rw::findChunk(stream, rw::ID_TEXDICTIONARY, nil, nil)){
+ return false;
+ }else{
+ def->texDict = rw::TexDictionary::streamRead(stream);
+ convertTxd(def->texDict);
+ return def->texDict != nil;
+ }
+}
+
+bool
+CTxdStore::LoadTxd(int slot, const char *filename)
+{
+ rw::StreamFile stream;
+ if(stream.open(getPath(filename), "rb")){
+ LoadTxd(slot, &stream);
+ stream.close();
+ return true;
+ }
+ printf("Failed to open TXD\n");
+ return false;
+}
+*/
+
+void
+CTxdStore::RemoveTxd(int slot)
+{
+ TxdDef *def = GetSlot(slot);
+ if(def->texDict)
+ RwTexDictionaryDestroy(def->texDict);
+ def->texDict = nil;
+}
+
+//bool
+//CTxdStore::isTxdLoaded(int slot)
+//{
+// return GetSlot(slot)->texDict != nil;
+//}
+
+STARTPATCHES
+ InjectHook(0x527440, CTxdStore::Initialize, PATCH_JUMP);
+ InjectHook(0x527470, CTxdStore::Shutdown, 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(0x527930, CTxdStore::AddRef, PATCH_JUMP);
+ InjectHook(0x527970, CTxdStore::RemoveRef, PATCH_JUMP);
+ InjectHook(0x5279C0, CTxdStore::RemoveRefWithoutDelete, PATCH_JUMP);
+ InjectHook(0x527870, CTxdStore::RemoveTxd, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/TxdStore.h b/src/TxdStore.h
new file mode 100644
index 00000000..2cfef0e0
--- /dev/null
+++ b/src/TxdStore.h
@@ -0,0 +1,34 @@
+#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 int AddTxdSlot(const char *name);
+ 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 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 void RemoveTxd(int slot);
+
+ static TxdDef *GetSlot(int slot) { return ms_pTxdPool->GetSlot(slot); }
+ static bool isTxdLoaded(int slot);
+};
diff --git a/src/Weather.cpp b/src/Weather.cpp
new file mode 100644
index 00000000..73421932
--- /dev/null
+++ b/src/Weather.cpp
@@ -0,0 +1,27 @@
+#include "common.h"
+#include "Weather.h"
+
+int32 &CWeather::SoundHandle = *(int32*)0x5FFBC4;
+
+int32 &CWeather::WeatherTypeInList = *(int32*)0x8F626C;
+int16 &CWeather::OldWeatherType = *(int16*)0x95CCEC;
+int16 &CWeather::NewWeatherType = *(int16*)0x95CC70;
+int16 &CWeather::ForcedWeatherType = *(int16*)0x95CC80;
+
+bool &CWeather::LightningFlash = *(bool*)0x95CDA3;
+bool &CWeather::LightningBurst = *(bool*)0x95CDAC;
+uint32 &CWeather::LightningStart = *(uint32*)0x8F5F84;
+uint32 &CWeather::LightningFlashLastChange = *(uint32*)0x8E2C0C;
+uint32 &CWeather::WhenToPlayLightningSound = *(uint32*)0x8F57E4;
+uint32 &CWeather::LightningDuration = *(uint32*)0x940578;
+
+float &CWeather::Foggyness = *(float*)0x885AF4;
+float &CWeather::CloudCoverage = *(float*)0x8E2818;
+float &CWeather::Wind = *(float*)0x8E2BF8;
+float &CWeather::Rain = *(float*)0x8E2BFC;
+float &CWeather::InterpolationValue = *(float*)0x8F2520;
+float &CWeather::WetRoads = *(float*)0x8F5FF8;
+float &CWeather::Rainbow = *(float*)0x940598;
+
+bool &CWeather::bScriptsForceRain = *(bool*)0x95CD7D;
+bool &CWeather::Stored_StateStored = *(bool*)0x95CDC1;
diff --git a/src/Weather.h b/src/Weather.h
new file mode 100644
index 00000000..41e07d93
--- /dev/null
+++ b/src/Weather.h
@@ -0,0 +1,35 @@
+enum {
+ WEATHER_SUNNY,
+ WEATHER_CLOUDY,
+ WEATHER_RAINY,
+ WEATHER_FOGGY
+};
+
+class CWeather
+{
+public:
+ static int32 &SoundHandle;
+
+ static int32 &WeatherTypeInList;
+ static int16 &OldWeatherType;
+ static int16 &NewWeatherType;
+ static int16 &ForcedWeatherType;
+
+ static bool &LightningFlash;
+ static bool &LightningBurst;
+ static uint32 &LightningStart;
+ static uint32 &LightningFlashLastChange;
+ static uint32 &WhenToPlayLightningSound;
+ static uint32 &LightningDuration;
+
+ static float &Foggyness;
+ static float &CloudCoverage;
+ static float &Wind;
+ static float &Rain;
+ static float &InterpolationValue;
+ static float &WetRoads;
+ static float &Rainbow;
+
+ static bool &bScriptsForceRain;
+ static bool &Stored_StateStored;
+};
diff --git a/src/World.cpp b/src/World.cpp
new file mode 100644
index 00000000..761d8bf6
--- /dev/null
+++ b/src/World.cpp
@@ -0,0 +1,39 @@
+#include "common.h"
+#include "patcher.h"
+#include "Entity.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;
+
+bool &CWorld::bNoMoreCollisionTorque = *(bool*)0x95CDCC;
+
+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;
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x4B1F60, CWorld::ClearScanCodes, PATCH_JUMP);
+ENDPATCHES
+
+WRAPPER CVector &FindPlayerCoors(CVector &v) { EAXJMP(0x4A1030); }
+WRAPPER CVehicle *FindPlayerVehicle(void) { EAXJMP(0x4A10C0); }
+WRAPPER CVehicle *FindPlayerTrain(void) { EAXJMP(0x4A1120); }
diff --git a/src/World.h b/src/World.h
new file mode 100644
index 00000000..ecd8feb3
--- /dev/null
+++ b/src/World.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "Game.h"
+#include "Lists.h"
+
+/* Sectors span from -2000 to 2000 in x and y.
+ * With 100x100 sectors, each is 40x40 units. */
+
+#define NUMSECTORS_X 100
+#define NUMSECTORS_Y 100
+
+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 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 bool &bNoMoreCollisionTorque;
+
+ 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 float GetSectorX(float f) { return ((f + 2000.0f)/40.0f); }
+ static float GetSectorY(float f) { return ((f + 2000.0f)/40.0f); }
+ static int GetSectorIndexX(float f) { return (int)GetSectorX(f); }
+ static int GetSectorIndexY(float f) { return (int)GetSectorY(f); }
+};
+
+CVector &FindPlayerCoors(CVector &v);
+class CVehicle;
+CVehicle *FindPlayerVehicle(void);
+CVehicle *FindPlayerTrain(void);
diff --git a/src/Zones.cpp b/src/Zones.cpp
new file mode 100644
index 00000000..6c8f66ce
--- /dev/null
+++ b/src/Zones.cpp
@@ -0,0 +1,614 @@
+#include "common.h"
+#include "patcher.h"
+#include "World.h"
+#include "Clock.h"
+#include "Zones.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; }
+
+
+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];
+ }
+ 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;
+ FindPlayerCoors(pos);
+ 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;
+ d = n - 1.0f;
+ }else{
+ d = (CClock::GetHours() - 5) / 3.0f;
+ n = d - 1.0f;
+ }
+ 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;
+}
+
+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];
+
+ 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;
+}
+
+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->gangThreshold[0] = gang0Density;
+ if(gang1Density != -1) info->gangThreshold[1] = gang1Density;
+ if(gang2Density != -1) info->gangThreshold[2] = gang2Density;
+ if(gang3Density != -1) info->gangThreshold[3] = gang3Density;
+ if(gang4Density != -1) info->gangThreshold[4] = gang4Density;
+ if(gang5Density != -1) info->gangThreshold[5] = gang5Density;
+ if(gang6Density != -1) info->gangThreshold[6] = gang6Density;
+ if(gang7Density != -1) info->gangThreshold[7] = gang7Density;
+ if(gang8Density != -1) info->gangThreshold[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;
+ }
+ }
+}
+
+STARTPATCHES
+ 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(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(0x4B8380, CTheZones::InitialiseAudioZoneArray, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/Zones.h b/src/Zones.h
new file mode 100644
index 00000000..c7745e29
--- /dev/null
+++ b/src/Zones.h
@@ -0,0 +1,107 @@
+#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;
+};
+
+class CZoneInfo
+{
+public:
+ // Car data
+ uint16 carDensity;
+ uint16 carThreshold[6];
+ uint16 copThreshold;
+ uint16 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 void AddZoneToAudioZoneArray(CZone *zone);
+ static void InitialiseAudioZoneArray(void);
+};
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 00000000..e55e5b52
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,97 @@
+#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: 4838) // narrowing conversion
+
+#include <stdint.h>
+#include <math.h>
+#include <assert.h>
+#include <new>
+
+#include <rwcore.h>
+#include <rpworld.h>
+
+#define rwVENDORID_ROCKSTAR 0x0253F2
+
+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;
+
+#define nil NULL
+
+#include "config.h"
+
+#define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1))
+
+// little hack
+extern void **rwengine;
+#define RwEngineInstance (*rwengine)
+
+// TODO
+struct RsInputDevice
+{
+ int inputDeviceType;
+ int used;
+ void *inputEventHandler;
+};
+
+struct RsGlobalType
+{
+ const char *appName;
+ int width;
+ int height;
+ int maximumWidth;
+ int maximumHeight;
+ int maxFPS;
+ int quit;
+ void *ps;
+ RsInputDevice keyboard;
+ RsInputDevice mouse;
+ RsInputDevice pad;
+};
+extern RsGlobalType &RsGlobal;
+
+#define SCREENW (RsGlobal.maximumWidth)
+#define SCREENH (RsGlobal.maximumHeight)
+
+struct GlobalScene
+{
+ RpWorld *world;
+ RwCamera *camera;
+};
+extern GlobalScene &Scene;
+
+#include "math/Vector.h"
+#include "math/Vector2D.h"
+#include "math/Matrix.h"
+#include "math/Rect.h"
+
+class CRGBA
+{
+public:
+ uint8 r, g, b, a;
+ CRGBA(void) { }
+ CRGBA(uint8 r, uint8 g, uint8 b, uint8 a) : r(r), g(g), b(b), a(a) { }
+};
+
+inline float
+clamp(float v, float min, float max){ return v<min ? min : v>max ? max : v; }
+inline float
+sq(float x) { return x*x; }
+#define PI M_PI
+#define DEGTORAD(d) (d/180.0f*PI)
+
+int myrand(void);
+void mysrand(unsigned int seed);
+
+#define debug printf
+
+//#define min(a, b) ((a) < (b) ? (a) : (b))
+//#define max(a, b) ((a) > (b) ? (a) : (b))
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 00000000..df99487f
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,56 @@
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+enum Config {
+ NUMCDIMAGES = 50, // was 12
+
+ MODELINFOSIZE = 5500,
+ TXDSTORESIZE = 850,
+ EXTRADIRSIZE = 128,
+
+ SIMPLEMODELSIZE = 5000,
+ TIMEMODELSIZE = 30,
+ CLUMPMODELSIZE = 5,
+ PEDMODELSIZE = 90,
+ VEHICLEMODELSIZE = 120,
+ TWODFXSIZE = 2000,
+
+ 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,
+
+
+ NUMPEDSTATS = 35,
+ NUMHANDLINGS = 57,
+
+ PATHNODESIZE = 4500,
+
+ NUMWEATHERS = 4,
+ NUMHOURS = 24,
+};
+
+#endif
diff --git a/src/debugmenu_public.h b/src/debugmenu_public.h
new file mode 100644
index 00000000..3671caca
--- /dev/null
+++ b/src/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 == 0){
+ 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 == 0)
+ 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/entities/Building.cpp b/src/entities/Building.cpp
new file mode 100644
index 00000000..e8f19b01
--- /dev/null
+++ b/src/entities/Building.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Building.h"
+#include "Pools.h"
+
+void *CBuilding::operator new(size_t sz) { return CPools::GetBuildingPool()->New(); }
+void CBuilding::operator delete(void *p, size_t sz) { CPools::GetBuildingPool()->Delete((CBuilding*)p); }
diff --git a/src/entities/Building.h b/src/entities/Building.h
new file mode 100644
index 00000000..d33aaa4f
--- /dev/null
+++ b/src/entities/Building.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Entity.h"
+
+class CBuilding : public CEntity
+{
+public:
+ // TODO: ReplaceWithNewModel
+ // TODO: ctor
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ virtual bool GetIsATreadable(void) { return false; }
+};
+static_assert(sizeof(CBuilding) == 0x64, "CBuilding: error");
diff --git a/src/entities/CutsceneHead.cpp b/src/entities/CutsceneHead.cpp
new file mode 100644
index 00000000..6a8874f5
--- /dev/null
+++ b/src/entities/CutsceneHead.cpp
@@ -0,0 +1,2 @@
+#include "common.h"
+#include "CutsceneHead.h"
diff --git a/src/entities/CutsceneHead.h b/src/entities/CutsceneHead.h
new file mode 100644
index 00000000..5784ffc9
--- /dev/null
+++ b/src/entities/CutsceneHead.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "CutsceneObject.h"
+
+class CCutsceneHead : public CCutsceneObject
+{
+public:
+ RwFrame *m_pHeadNode;
+};
+static_assert(sizeof(CCutsceneHead) == 0x19C, "CCutsceneHead: error");
diff --git a/src/entities/CutsceneObject.cpp b/src/entities/CutsceneObject.cpp
new file mode 100644
index 00000000..6aa0f4b3
--- /dev/null
+++ b/src/entities/CutsceneObject.cpp
@@ -0,0 +1,2 @@
+#include "common.h"
+#include "CutsceneObject.h"
diff --git a/src/entities/CutsceneObject.h b/src/entities/CutsceneObject.h
new file mode 100644
index 00000000..c5cbf83f
--- /dev/null
+++ b/src/entities/CutsceneObject.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Object.h"
+
+class CCutsceneObject : public CObject
+{
+public:
+};
+static_assert(sizeof(CCutsceneObject) == 0x198, "CCutsceneObject: error");
diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp
new file mode 100644
index 00000000..11fa9ab2
--- /dev/null
+++ b/src/entities/Entity.cpp
@@ -0,0 +1,391 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Placeable.h"
+#include "Entity.h"
+#include "Lights.h"
+#include "World.h"
+#include "Camera.h"
+#include "References.h"
+#include "TxdStore.h"
+#include "Zones.h"
+#include "patcher.h"
+
+int gBuildings;
+
+void
+CEntity::GetBoundCentre(CVector &out)
+{
+ out = m_matrix * CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.center;
+};
+
+bool
+CEntity::GetIsTouching(CVector const &center, float radius)
+{
+ return sq(GetBoundRadius()+radius) > (GetBoundCentre()-center).MagnitudeSqr();
+}
+
+bool
+CEntity::GetIsOnScreen(void)
+{
+ return TheCamera.IsSphereVisible(GetBoundCentre(), GetBoundRadius(),
+ &TheCamera.GetCameraMatrix());
+}
+
+bool
+CEntity::GetIsOnScreenComplex(void)
+{
+ RwV3d boundBox[8];
+
+ if(TheCamera.IsPointVisible(GetBoundCentre(), &TheCamera.GetCameraMatrix()))
+ return true;
+
+ CRect rect = GetBoundRect();
+ CColModel *colmodel = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel();
+ float z = GetPosition().z;
+ float minz = z + colmodel->boundingBox.min.z;
+ float maxz = z + colmodel->boundingBox.max.z;
+ boundBox[0].x = rect.left;
+ boundBox[0].y = rect.top;
+ boundBox[0].z = minz;
+ boundBox[1].x = rect.left;
+ boundBox[1].y = rect.bottom;
+ boundBox[1].z = minz;
+ boundBox[2].x = rect.right;
+ boundBox[2].y = rect.top;
+ boundBox[2].z = minz;
+ boundBox[3].x = rect.right;
+ boundBox[3].y = rect.bottom;
+ boundBox[3].z = minz;
+ boundBox[4].x = rect.left;
+ boundBox[4].y = rect.top;
+ boundBox[4].z = maxz;
+ boundBox[5].x = rect.left;
+ boundBox[5].y = rect.bottom;
+ boundBox[5].z = maxz;
+ boundBox[6].x = rect.right;
+ boundBox[6].y = rect.top;
+ boundBox[6].z = maxz;
+ boundBox[7].x = rect.right;
+ boundBox[7].y = rect.bottom;
+ boundBox[7].z = maxz;
+
+ return TheCamera.IsBoxVisible(boundBox, &TheCamera.GetCameraMatrix());
+}
+
+void
+CEntity::Add(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP];
+ break;
+ }
+ list->InsertItem(this);
+ }
+}
+
+void
+CEntity::Remove(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_BUILDING:
+ list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP];
+ break;
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ case ENTITY_TYPE_DUMMY:
+ list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP];
+ break;
+ }
+ list->RemoveItem(this);
+ }
+}
+
+void
+CEntity::CreateRwObject(void)
+{
+ CBaseModelInfo *mi;
+
+ mi = CModelInfo::GetModelInfo(m_modelIndex);
+ m_rwObject = mi->CreateInstance();
+ if(m_rwObject){
+ if(IsBuilding())
+ gBuildings++;
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ m_matrix.AttachRW(RwFrameGetMatrix(RpAtomicGetFrame(m_rwObject)), false);
+ else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ m_matrix.AttachRW(RwFrameGetMatrix(RpClumpGetFrame(m_rwObject)), false);
+ mi->AddRef();
+ }
+}
+
+void
+CEntity::DeleteRwObject(void)
+{
+ RwFrame *f;
+
+ m_matrix.Detach();
+ if(m_rwObject){
+ if(RwObjectGetType(m_rwObject) == rpATOMIC){
+ f = RpAtomicGetFrame(m_rwObject);
+ RpAtomicDestroy((RpAtomic*)m_rwObject);
+ RwFrameDestroy(f);
+ }else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ RpClumpDestroy((RpClump*)m_rwObject);
+ m_rwObject = nil;
+ CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef();
+ if(IsBuilding())
+ gBuildings--;
+ }
+}
+
+void
+CEntity::UpdateRwFrame(void)
+{
+ if(m_rwObject){
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ RwFrameUpdateObjects(RpAtomicGetFrame(m_rwObject));
+ else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+ RwFrameUpdateObjects(RpClumpGetFrame(m_rwObject));
+ }
+}
+
+void
+CEntity::SetupBigBuilding(void)
+{
+ CSimpleModelInfo *mi;
+
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex);
+ bIsBIGBuilding = true;
+ m_flagC20 = true;
+ bUsesCollision = false;
+ m_level = CTheZones::GetLevelFromPosition(GetPosition());
+ if(m_level == LEVEL_NONE){
+ if(mi->GetTxdSlot() != CTxdStore::FindTxdSlot("generic")){
+ mi->SetTexDictionary("generic");
+ printf("%d:%s txd has been set to generic\n", m_modelIndex, mi->GetName());
+ }
+ }
+ if(mi->m_lodDistances[0] > 2000.0f)
+ m_level = LEVEL_NONE;
+}
+
+CRect
+CEntity::GetBoundRect(void)
+{
+ CRect rect;
+ CVector v;
+ CColModel *col = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel();
+
+ rect.ContainPoint(m_matrix * col->boundingBox.min);
+ rect.ContainPoint(m_matrix * col->boundingBox.max);
+
+ v = col->boundingBox.min;
+ v.x = col->boundingBox.max.x;
+ rect.ContainPoint(m_matrix * v);
+
+ v = col->boundingBox.max;
+ v.x = col->boundingBox.min.x;
+ rect.ContainPoint(m_matrix * v);
+
+ return rect;
+}
+
+void
+CEntity::PreRender(void)
+{
+}
+
+void
+CEntity::Render(void)
+{
+ if(m_rwObject){
+ bImBeingRendered = true;
+ if(RwObjectGetType(m_rwObject) == rpATOMIC)
+ RpAtomicRender((RpAtomic*)m_rwObject);
+ else
+ RpClumpRender((RpClump*)m_rwObject);
+ bImBeingRendered = false;
+ }
+}
+
+bool
+CEntity::SetupLighting(void)
+{
+ DeActivateDirectional();
+ SetAmbientColours();
+ return false;
+}
+
+void
+CEntity::RegisterReference(CEntity **pent)
+{
+ if(IsBuilding())
+ return;
+ CReference *ref;
+ // check if already registered
+ for(ref = m_pFirstReference; ref; ref = ref->next)
+ if(ref->pentity == pent)
+ return;
+ // have to allocate new reference
+ ref = CReferences::pEmptyList;
+ if(ref){
+ CReferences::pEmptyList = ref->next;
+
+ ref->pentity = pent;
+ ref->next = m_pFirstReference;
+ m_pFirstReference = ref;
+ return;
+ }
+ return;
+}
+
+// Clear all references to this entity
+void
+CEntity::ResolveReferences(void)
+{
+ CReference *ref;
+ // clear pointers to this entity
+ for(ref = m_pFirstReference; ref; ref = ref->next)
+ if(*ref->pentity == this)
+ *ref->pentity = nil;
+ // free list
+ if(m_pFirstReference){
+ for(ref = m_pFirstReference; ref->next; ref = ref->next)
+ ;
+ ref->next = CReferences::pEmptyList;
+ CReferences::pEmptyList = ref;
+ m_pFirstReference = nil;
+ }
+}
+
+// Free all references that no longer point to this entity
+void
+CEntity::PruneReferences(void)
+{
+ CReference *ref, *next, **lastnextp;
+ lastnextp = &m_pFirstReference;
+ for(ref = m_pFirstReference; ref; ref = next){
+ next = ref->next;
+ if(*ref->pentity == this)
+ lastnextp = &ref->next;
+ else{
+ *lastnextp = ref->next;
+ ref->next = CReferences::pEmptyList;
+ CReferences::pEmptyList = ref;
+ }
+ }
+}
+
+STARTPATCHES
+ InjectHook(0x4742C0, (void (CEntity::*)(CVector&))&CEntity::GetBoundCentre, PATCH_JUMP);
+ InjectHook(0x474310, &CEntity::GetBoundRadius, PATCH_JUMP);
+ InjectHook(0x474C10, &CEntity::GetIsTouching, PATCH_JUMP);
+ InjectHook(0x474CC0, &CEntity::GetIsOnScreen, PATCH_JUMP);
+ InjectHook(0x474D20, &CEntity::GetIsOnScreenComplex, PATCH_JUMP);
+ InjectHook(0x474CA0, &CEntity::IsVisible, PATCH_JUMP);
+ InjectHook(0x474330, &CEntity::UpdateRwFrame, PATCH_JUMP);
+ InjectHook(0x4755E0, &CEntity::SetupBigBuilding, PATCH_JUMP);
+ InjectHook(0x4A7480, &CEntity::RegisterReference, PATCH_JUMP);
+ InjectHook(0x4A74E0, &CEntity::ResolveReferences, PATCH_JUMP);
+ InjectHook(0x4A7530, &CEntity::PruneReferences, PATCH_JUMP);
+
+ InjectHook(0x475080, &CEntity::Add_, PATCH_JUMP);
+ InjectHook(0x475310, &CEntity::Remove_, PATCH_JUMP);
+ InjectHook(0x473EA0, &CEntity::CreateRwObject_, PATCH_JUMP);
+ InjectHook(0x473F90, &CEntity::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x474000, &CEntity::GetBoundRect_, PATCH_JUMP);
+ InjectHook(0x474BD0, &CEntity::Render_, PATCH_JUMP);
+ InjectHook(0x4A7C60, &CEntity::SetupLighting_, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/entities/Entity.h b/src/entities/Entity.h
new file mode 100644
index 00000000..8bcd7348
--- /dev/null
+++ b/src/entities/Entity.h
@@ -0,0 +1,146 @@
+#pragma once
+
+#include "ModelInfo.h"
+#include "Placeable.h"
+
+struct CReference;
+
+enum eEntityType
+{
+ ENTITY_TYPE_NOTHING = 0,
+ ENTITY_TYPE_BUILDING,
+ ENTITY_TYPE_VEHICLE,
+ ENTITY_TYPE_PED,
+ ENTITY_TYPE_OBJECT,
+ ENTITY_TYPE_DUMMY,
+ ENTITY_TYPE_6,
+ ENTITY_TYPE_7,
+};
+
+enum eEntityStatus
+{
+ // from SA MTA! let's hope they didn't change from III
+ STATUS_PLAYER = 0,
+ STATUS_PLAYER_PLAYBACKFROMBUFFER,
+ STATUS_SIMPLE,
+ STATUS_PHYSICS,
+ STATUS_ABANDONED,
+ STATUS_WRECKED,
+ STATUS_TRAIN_MOVING,
+ STATUS_TRAIN_NOT_MOVING,
+ STATUS_HELI,
+ STATUS_PLANE,
+ STATUS_PLAYER_REMOTE,
+ STATUS_PLAYER_DISABLED,
+ //STATUS_TRAILER,
+ //STATUS_SIMPLE_TRAILER
+};
+
+class CEntity : public CPlaceable
+{
+public:
+ RwObject *m_rwObject;
+ uint32 m_type : 3;
+ uint32 m_status : 5;
+
+ // flagsA
+ uint32 bUsesCollision : 1;
+ uint32 bCollisionProcessed : 1;
+ uint32 bIsStatic : 1;
+ uint32 bHasContacted : 1;
+ uint32 bPedPhysics : 1;
+ uint32 bIsStuck : 1;
+ uint32 bIsInSafePosition : 1;
+ uint32 bUseCollisionRecords : 1;
+
+ // flagsB
+ uint32 bWasPostponed : 1;
+ uint32 m_flagB2 : 1; // explosion proof?
+ uint32 bIsVisible : 1;
+ uint32 bHasCollided : 1; //
+ uint32 bRenderScorched : 1;
+ uint32 m_flagB20 : 1; // bFlashing?
+ uint32 bIsBIGBuilding : 1;
+ // VC inserts one more flag here: if drawdist <= 2000
+ uint32 bRenderDamaged : 1;
+
+ // flagsC
+ uint32 m_flagC1 : 1; // bullet proof?
+ uint32 m_flagC2 : 1; // fire proof?
+ uint32 m_flagC4 : 1; // collision proof?
+ uint32 m_flagC8 : 1; // melee proof?
+ uint32 m_flagC10 : 1; // bOnlyDamagedByPlayer?
+ uint32 m_flagC20 : 1;
+ uint32 m_bZoneCulled : 1;
+ uint32 m_bZoneCulled2 : 1; // only treadables+10m
+
+ // flagsD
+ uint32 bRemoveFromWorld : 1;
+ uint32 bHasHitWall : 1;
+ uint32 bImBeingRendered : 1;
+ uint32 m_flagD8 : 1;
+ uint32 m_flagD10 : 1;
+ uint32 bDrawLast : 1;
+ uint32 m_flagD40 : 1;
+ uint32 m_flagD80 : 1;
+
+ // flagsE
+ uint32 bDistanceFade : 1;
+ uint32 m_flagE2 : 1;
+
+ uint16 m_scanCode;
+ int16 m_randomSeed;
+ int16 m_modelIndex;
+ uint16 m_level; // int16
+ CReference *m_pFirstReference;
+
+ virtual void Add(void);
+ virtual void Remove(void);
+ virtual void SetModelIndex(uint32 i) { m_modelIndex = i; CreateRwObject(); }
+ virtual void SetModelIndexNoCreate(uint32 i) { m_modelIndex = i; }
+ virtual void CreateRwObject(void);
+ virtual void DeleteRwObject(void);
+ virtual CRect GetBoundRect(void);
+ virtual void ProcessControl(void) {}
+ virtual void ProcessCollision(void) {}
+ virtual void ProcessShift(void) {}
+ virtual void Teleport(CVector v) {}
+ virtual void PreRender(void);
+ virtual void Render(void);
+ virtual bool SetupLighting(void);
+ virtual void RemoveLighting(bool) {}
+ virtual void FlagToDestroyWhenNextProcessed(void) {}
+
+ bool IsBuilding(void) { return m_type == ENTITY_TYPE_BUILDING; }
+ bool IsVehicle(void) { return m_type == ENTITY_TYPE_VEHICLE; }
+ bool IsPed(void) { return m_type == ENTITY_TYPE_PED; }
+ bool IsObject(void) { return m_type == ENTITY_TYPE_OBJECT; }
+ bool IsDummy(void) { return m_type == ENTITY_TYPE_DUMMY; }
+
+ void GetBoundCentre(CVector &out);
+ CVector GetBoundCentre(void) { CVector v; GetBoundCentre(v); return v; }
+ float GetBoundRadius(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel()->boundingSphere.radius; }
+ bool GetIsTouching(CVector const &center, float r);
+ bool GetIsOnScreen(void);
+ bool GetIsOnScreenComplex(void);
+ bool IsVisible(void) { return m_rwObject && bIsVisible && GetIsOnScreen(); }
+ bool IsVisibleComplex(void) { return m_rwObject && bIsVisible && GetIsOnScreenComplex(); }
+ int GetModelIndex(void) { return m_modelIndex; }
+ void UpdateRwFrame(void);
+ void SetupBigBuilding(void);
+
+ void RegisterReference(CEntity **pent);
+ void ResolveReferences(void);
+ void PruneReferences(void);
+
+
+ // to make patching virtual functions possible
+ void Add_(void) { CEntity::Add(); }
+ void Remove_(void) { CEntity::Remove(); }
+ void CreateRwObject_(void) { CEntity::CreateRwObject(); }
+ void DeleteRwObject_(void) { CEntity::DeleteRwObject(); }
+ CRect GetBoundRect_(void) { return CEntity::GetBoundRect(); }
+ void Render_(void) { CEntity::Render(); }
+ bool SetupLighting_(void) { return CEntity::SetupLighting(); }
+};
+static_assert(sizeof(CEntity) == 0x64, "CEntity: error");
diff --git a/src/entities/Object.cpp b/src/entities/Object.cpp
new file mode 100644
index 00000000..8ce1250f
--- /dev/null
+++ b/src/entities/Object.cpp
@@ -0,0 +1,9 @@
+#include "common.h"
+#include "patcher.h"
+#include "Object.h"
+#include "Pools.h"
+
+void *CObject::operator new(size_t sz) { return CPools::GetObjectPool()->New(); }
+void CObject::operator delete(void *p, size_t sz) { CPools::GetObjectPool()->Delete((CObject*)p); }
+
+WRAPPER void CObject::ObjectDamage(float amount) { EAXJMP(0x4BB240); }
diff --git a/src/entities/Object.h b/src/entities/Object.h
new file mode 100644
index 00000000..6992b92d
--- /dev/null
+++ b/src/entities/Object.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "Physical.h"
+
+enum {
+ GAME_OBJECT = 1,
+ MISSION_OBJECT = 2,
+ TEMP_OBJECT = 3,
+};
+
+class CObject : public CPhysical
+{
+public:
+ CMatrix m_objectMatrix;
+ float m_fUprootLimit;
+ int8 ObjectCreatedBy;
+// int8 m_nObjectFlags;
+ int8 m_obj_flag1 : 1;
+ int8 m_obj_flag2 : 1;
+ int8 m_obj_flag4 : 1;
+ int8 m_obj_flag8 : 1;
+ int8 m_obj_flag10 : 1;
+ int8 bHasBeenDamaged : 1;
+ int8 m_obj_flag40 : 1;
+ int8 m_obj_flag80 : 1;
+ int8 field_172;
+ int8 field_173;
+ float m_fCollisionDamageMultiplier;
+ int8 m_nCollisionDamageEffect;
+ int8 m_bSpecialCollisionResponseCases;
+ int8 m_bCameraToAvoidThisObject;
+ int8 field_17B;
+ int8 field_17C;
+ int8 field_17D;
+ int8 field_17E;
+ int8 field_17F;
+ int32 m_nEndOfLifeTime;
+ int16 m_nRefModelIndex;
+ int8 field_186;
+ int8 field_187;
+ CEntity *m_pCurSurface;
+ CEntity *field_18C;
+ int8 m_colour1, m_colour2;
+
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ void ObjectDamage(float amount);
+};
+static_assert(sizeof(CObject) == 0x198, "CObject: error");
diff --git a/src/entities/Ped.h b/src/entities/Ped.h
new file mode 100644
index 00000000..fd71b616
--- /dev/null
+++ b/src/entities/Ped.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "Physical.h"
+
+enum PedAction
+{
+ PED_PASSENGER = 44,
+};
+
+class CVehicle;
+
+class CPed : public CPhysical
+{
+public:
+ // 0x128
+ uint8 stuff1[252];
+ int32 m_nPedState;
+ uint8 stuff2[196];
+ CEntity *m_pCurrentPhysSurface;
+ CVector m_vecOffsetFromPhysSurface;
+ CEntity *m_pCurSurface;
+ uint8 stuff3[16];
+ CVehicle *m_pMyVehicle;
+ bool bInVehicle;
+ uint8 stuff4[23];
+ int32 m_nPedType;
+ uint8 stuff5[528];
+
+ bool IsPlayer(void) { return m_nPedType == 0 || m_nPedType== 1 || m_nPedType == 2 || m_nPedType == 3; }
+};
+static_assert(offsetof(CPed, m_nPedState) == 0x224, "CPed: error");
+static_assert(offsetof(CPed, m_pCurSurface) == 0x2FC, "CPed: error");
+static_assert(offsetof(CPed, m_pMyVehicle) == 0x310, "CPed: error");
+static_assert(offsetof(CPed, m_nPedType) == 0x32C, "CPed: error");
+static_assert(sizeof(CPed) == 0x540, "CPed: error");
diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp
new file mode 100644
index 00000000..f235cb42
--- /dev/null
+++ b/src/entities/Physical.cpp
@@ -0,0 +1,916 @@
+#include "common.h"
+#include "patcher.h"
+#include "World.h"
+#include "Timer.h"
+#include "ModelIndices.h"
+#include "Vehicle.h"
+#include "Ped.h"
+#include "Object.h"
+#include "Glass.h"
+#include "ParticleObject.h"
+#include "Particle.h"
+#include "SurfaceTable.h"
+#include "Physical.h"
+
+void
+CPhysical::Add(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ default:
+ assert(0);
+ }else switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ default:
+ assert(0);
+ }
+ CPtrNode *node = list->InsertItem(this);
+ assert(node);
+ m_entryInfoList.InsertItem(list, node, s);
+ }
+}
+
+void
+CPhysical::Remove(void)
+{
+ CEntryInfoNode *node, *next;
+ for(node = m_entryInfoList.first; node; node = next){
+ next = node->next;
+ node->list->DeleteNode(node->listnode);
+ m_entryInfoList.DeleteNode(node);
+ }
+}
+
+void
+CPhysical::RemoveAndAdd(void)
+{
+ int x, xstart, xmid, xend;
+ int y, ystart, ymid, yend;
+ CSector *s;
+ CPtrList *list;
+
+ CRect bounds = GetBoundRect();
+ xstart = CWorld::GetSectorIndexX(bounds.left);
+ xend = CWorld::GetSectorIndexX(bounds.right);
+ xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f);
+ ystart = CWorld::GetSectorIndexY(bounds.bottom);
+ yend = CWorld::GetSectorIndexY(bounds.top);
+ ymid = CWorld::GetSectorIndexY((bounds.bottom + bounds.top)/2.0f);
+ assert(xstart >= 0);
+ assert(xend < NUMSECTORS_X);
+ assert(ystart >= 0);
+ assert(yend < NUMSECTORS_Y);
+
+ // we'll try to recycle nodes from here
+ CEntryInfoNode *next = m_entryInfoList.first;
+
+ for(y = ystart; y <= yend; y++)
+ for(x = xstart; x <= xend; x++){
+ s = CWorld::GetSector(x, y);
+ if(x == xmid && y == ymid) switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS];
+ break;
+ }else switch(m_type){
+ case ENTITY_TYPE_VEHICLE:
+ list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP];
+ break;
+ case ENTITY_TYPE_PED:
+ list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP];
+ break;
+ case ENTITY_TYPE_OBJECT:
+ list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP];
+ break;
+ }
+ if(next){
+ // If we still have old nodes, use them
+ next->list->RemoveNode(next->listnode);
+ list->InsertNode(next->listnode);
+ next->list = list;
+ next->sector = s;
+ next = next->next;
+ }else{
+ CPtrNode *node = list->InsertItem(this);
+ m_entryInfoList.InsertItem(list, node, s);
+ }
+ }
+
+ // Remove old nodes we no longer need
+ CEntryInfoNode *node;
+ for(node = next; node; node = next){
+ next = node->next;
+ node->list->DeleteNode(node->listnode);
+ m_entryInfoList.DeleteNode(node);
+ }
+}
+
+CRect
+CPhysical::GetBoundRect(void)
+{
+ CVector center;
+ float radius;
+ GetBoundCentre(center);
+ radius = GetBoundRadius();
+ return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius);
+}
+
+void
+CPhysical::AddToMovingList(void)
+{
+ m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this);
+}
+
+void
+CPhysical::RemoveFromMovingList(void)
+{
+ if(m_movingListNode){
+ CWorld::GetMovingEntityList().DeleteNode(m_movingListNode);
+ m_movingListNode = nil;
+ }
+}
+
+
+/*
+ * Some quantities (german in parens):
+ *
+ * acceleration: distance/time^2: a
+ * velocity: distance/time: v (GTA: speed)
+ * momentum (impuls): velocity*mass: p
+ * impulse (kraftstoss): delta momentum, force*time: J
+ *
+ * angular equivalents:
+ * velocity -> angular velocity (GTA: turn speed)
+ * momentum -> angular momentum (drehimpuls): L = r cross p
+ * force -> torque (drehmoment): tau = r cross F
+ * mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass)
+ */
+
+CVector
+CPhysical::GetSpeed(const CVector &r)
+{
+ return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r);
+}
+
+void
+CPhysical::ApplyMoveSpeed(void)
+{
+ GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep();
+}
+
+void
+CPhysical::ApplyTurnSpeed(void)
+{
+ // Move the coordinate axes by their speed
+ // Note that this denormalizes the matrix
+ CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep();
+ GetRight() += CrossProduct(turnvec, GetRight());
+ GetForward() += CrossProduct(turnvec, GetForward());
+ GetUp() += CrossProduct(turnvec, GetUp());
+}
+
+void
+CPhysical::ApplyMoveForce(float jx, float jy, float jz)
+{
+ m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass);
+}
+
+void
+CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz)
+{
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
+ m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass);
+}
+
+void
+CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz)
+{
+ m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass);
+}
+
+void
+CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz)
+{
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz));
+ m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass);
+}
+
+void
+CPhysical::ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3)
+{
+ if(1.0f - f2 <= 0.0f)
+ return;
+ float step = min(CTimer::GetTimeStep(), 3.0f);
+ float strength = -0.008f*m_fMass*2.0f*step * f1 * (1.0f-f2) * f3;
+ ApplyMoveForce(v.x*strength, v.y*strength, v.z*strength);
+ ApplyTurnForce(v.x*strength, v.y*strength, v.z*strength, p.x, p.y, p.z);
+}
+
+void
+CPhysical::ApplyGravity(void)
+{
+ if(bAffectedByGravity)
+ m_vecMoveSpeed.z -= 0.008f * CTimer::GetTimeStep();
+}
+
+void
+CPhysical::ApplyFriction(void)
+{
+ m_vecMoveSpeed += m_vecMoveFriction;
+ m_vecTurnSpeed += m_vecTurnFriction;
+ m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f);
+}
+
+void
+CPhysical::ApplyAirResistance(void)
+{
+ if(m_fAirResistance > 0.1f){
+ float f = powf(m_fAirResistance, CTimer::GetTimeStep());
+ m_vecMoveSpeed *= f;
+ m_vecTurnSpeed *= f;
+ }else{
+ float f = powf(1.0f/(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep());
+ m_vecMoveSpeed *= f;
+ m_vecTurnSpeed *= 0.99f;
+ }
+}
+
+
+bool
+CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB)
+{
+ float eA, eB;
+ CPhysical *A = this;
+ CObject *Bobj = (CObject*)B;
+
+ bool ispedcontactA = false;
+ bool ispedcontactB = false;
+
+ float timestepA;
+ if(B->bPedPhysics){
+ timestepA = 10.0f;
+ if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A)
+ ispedcontactA = true;
+ }else
+ timestepA = A->m_phy_flagA1 ? 2.0f : 1.0f;
+
+ float timestepB;
+ if(A->bPedPhysics){
+ if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() &&
+ (B->m_status == STATUS_ABANDONED || B->m_status == STATUS_WRECKED || A->bHasHitWall))
+ timestepB = 2200.0f / B->m_fMass;
+ else
+ timestepB = 10.0f;
+
+ if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B)
+ ispedcontactB = true;
+ }else
+ timestepB = B->m_phy_flagA1 ? 2.0f : 1.0f;
+
+ float speedA, speedB;
+ if(B->bIsStatic){
+ if(A->bPedPhysics){
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ if(speedA < 0.0f){
+ if(B->IsObject()){
+ impulseA = -speedA * A->m_fMass;
+ impulseB = impulseA;
+ if(impulseA > Bobj->m_fUprootLimit){
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
+ else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+ }else{
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToSoftCollision(B, impulseA);
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
+ return true;
+ }
+ }else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+
+ if(B->bInfiniteMass){
+ impulseA = -speedA * A->m_fMass;
+ impulseB = 0.0f;
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA);
+ return true;
+ }
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ if(speedA < 0.0f){
+ if(B->IsObject()){
+ if(A->bHasHitWall)
+ eA = -1.0f;
+ else
+ eA = -(1.0f + A->m_fElasticity);
+ impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal);
+ impulseB = impulseA;
+
+ if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){
+ Bobj->ObjectDamage(impulseA);
+ if(!B->bUsesCollision){
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(colpoint.normal*0.2f*impulseA);
+ A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA);
+ }
+ return false;
+ }
+ }
+
+ if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) &&
+ !B->bInfiniteMass){
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false);
+ else
+ B->bIsStatic = false;
+ int16 model = B->GetModelIndex();
+ if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){
+ CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true);
+ Bobj->bHasBeenDamaged = true;
+ }else if(B->IsObject() && model != MI_EXPLODINGBARREL && model != MI_PETROLPUMP)
+ Bobj->bHasBeenDamaged = true;
+ }else{
+ if(IsGlass(B->GetModelIndex()))
+ CGlass::WindowRespondsToSoftCollision(B, impulseA);
+ CVector f = colpoint.normal * impulseA;
+ if(A->IsVehicle() && colpoint.normal.z < 0.7f)
+ f.z *= 0.3f;
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(f);
+ if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque)
+ A->ApplyTurnForce(f, pointposA);
+ }
+ return true;
+ }
+ }else if(!B->bInfiniteMass)
+ B->bIsStatic = false;
+ }
+ }
+
+ if(B->bIsStatic)
+ return false;
+ if(!B->bInfiniteMass)
+ B->AddToMovingList();
+ }
+
+ // B is not static
+
+ if(A->bPedPhysics && B->bPedPhysics){
+ // negative if A is moving towards B
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ // positive if B is moving towards A
+ // not interested in how much B moves into A apparently?
+ // only interested in cases where A collided into B
+ speedB = max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal));
+ // A has moved into B
+ if(speedA < speedB){
+ if(!A->bHasHitWall)
+ speedB -= (speedA - speedB) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (speedB-speedA) * A->m_fMass * timestepA;
+ if(!A->bInfiniteMass)
+ A->ApplyMoveForce(colpoint.normal*(impulseA/timestepA));
+ return true;
+ }
+ }else if(A->bPedPhysics){
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
+
+ float a = A->m_fMass*timestepA;
+ float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(!A->bInfiniteMass){
+ if(fA.z < 0.0f) fA.z = 0.0f;
+ if(ispedcontactB){
+ fA.x *= 2.0f;
+ fA.y *= 2.0f;
+ }
+ A->ApplyMoveForce(fA);
+ }
+ if(!B->bInfiniteMass && !ispedcontactB){
+ B->ApplyMoveForce(fB);
+ B->ApplyTurnForce(fB, pointposB);
+ }
+ return true;
+ }
+ }else if(B->bPedPhysics){
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+
+ float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
+ float b = B->m_fMass*timestepB;
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(!A->bInfiniteMass && !ispedcontactA){
+ if(fA.z < 0.0f) fA.z = 0.0f;
+ A->ApplyMoveForce(fA);
+ A->ApplyTurnForce(fA, pointposA);
+ }
+ if(!B->bInfiniteMass){
+ if(fB.z < 0.0f){
+ fB.z = 0.0f;
+ if(fabs(speedA) < 0.01f)
+ fB *= 0.5f;
+ }
+ if(ispedcontactA){
+ fB.x *= 2.0f;
+ fB.y *= 2.0f;
+ }
+ B->ApplyMoveForce(fB);
+ }
+ return true;
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal);
+ speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal);
+ float a = A->GetMassTime(pointposA, colpoint.normal, timestepA);
+ float b = B->GetMassTime(pointposB, colpoint.normal, timestepB);
+ float speedSum = (b*speedB + a*speedA)/(a + b);
+ if(speedA < speedSum){
+ if(A->bHasHitWall)
+ eA = speedSum;
+ else
+ eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ if(B->bHasHitWall)
+ eB = speedSum;
+ else
+ eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f;
+ impulseA = (eA - speedA) * a;
+ impulseB = -(eB - speedB) * b;
+ CVector fA = colpoint.normal*(impulseA/timestepA);
+ CVector fB = colpoint.normal*(-impulseB/timestepB);
+ if(A->IsVehicle() && !A->bHasHitWall){
+ fA.x *= 1.4f;
+ fA.y *= 1.4f;
+ if(colpoint.normal.z < 0.7f)
+ fA.z *= 0.3f;
+ if(A->m_status == STATUS_PLAYER)
+ pointposA *= 0.8f;
+ if(CWorld::bNoMoreCollisionTorque){
+ A->ApplyFrictionMoveForce(fA*-0.3f);
+ A->ApplyFrictionTurnForce(fA*-0.3f, pointposA);
+ }
+ }
+ if(B->IsVehicle() && !B->bHasHitWall){
+ fB.x *= 1.4f;
+ fB.y *= 1.4f;
+ if(colpoint.normal.z < 0.7f)
+ fB.z *= 0.3f;
+ if(B->m_status == STATUS_PLAYER)
+ pointposB *= 0.8f;
+ if(CWorld::bNoMoreCollisionTorque){
+ // BUG: the game actually uses A here, but this can't be right
+ B->ApplyFrictionMoveForce(fB*-0.3f);
+ B->ApplyFrictionTurnForce(fB*-0.3f, pointposB);
+ }
+ }
+ if(!A->bInfiniteMass){
+ A->ApplyMoveForce(fA);
+ A->ApplyTurnForce(fA, pointposA);
+ }
+ if(!B->bInfiniteMass){
+ if(B->bIsInSafePosition)
+ B->UnsetIsInSafePosition();
+ B->ApplyMoveForce(fB);
+ B->ApplyTurnForce(fB, pointposB);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed)
+{
+ float normalSpeed;
+ float e;
+ CVector speed;
+ CVector vImpulse;
+
+ if(bPedPhysics){
+ normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
+ if(normalSpeed < 0.0f){
+ impulse = -normalSpeed * m_fMass;
+ ApplyMoveForce(colpoint.normal * impulse);
+ return true;
+ }
+ }else{
+ CVector pointpos = colpoint.point - GetPosition();
+ speed = GetSpeed(pointpos);
+ normalSpeed = DotProduct(speed, colpoint.normal);
+ if(normalSpeed < 0.0f){
+ float minspeed = 0.0104f * CTimer::GetTimeStep();
+ if((IsObject() || IsVehicle() && GetUp().z < -0.3f) &&
+ !bHasContacted &&
+ fabs(m_vecMoveSpeed.x) < minspeed &&
+ fabs(m_vecMoveSpeed.y) < minspeed &&
+ fabs(m_vecMoveSpeed.z) < minspeed*2.0f)
+ e = -1.0f;
+ else
+ e = -(m_fElasticity + 1.0f);
+ impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal);
+
+ // ApplyMoveForce
+ vImpulse = colpoint.normal*impulse;
+ if(IsVehicle() &&
+ (!bHasHitWall ||
+ !(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass))))
+ moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass);
+ else
+ moveSpeed += vImpulse * (1.0f/m_fMass);
+
+ // ApplyTurnForce
+ CVector com = Multiply3x3(m_matrix, m_vecCentreOfMass);
+ CVector turnimpulse = CrossProduct(pointpos-com, vImpulse);
+ turnSpeed += turnimpulse*(1.0f/m_fTurnMass);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
+{
+ CVector speedA, speedB;
+ float normalSpeedA, normalSpeedB;
+ CVector vOtherSpeedA, vOtherSpeedB;
+ float fOtherSpeedA, fOtherSpeedB;
+ float speedSum;
+ CVector frictionDir;
+ float impulseA, impulseB;
+ float impulseLimit;
+ CPhysical *A = this;
+
+ if(A->bPedPhysics && B->bPedPhysics){
+ normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
+ impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit; // BUG: game has A's clamp again here, but this can't be right
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ return true;
+ }
+ }else if(A->bPedPhysics){
+ if(B->IsVehicle())
+ return false;
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedB = B->GetSpeed(pointposB);
+
+ normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal);
+ normalSpeedB = DotProduct(speedB, colpoint.normal);
+ vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massB = B->GetMass(pointposB, frictionDir);
+ speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
+ impulseB = (speedSum - fOtherSpeedB) * massB;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
+ return true;
+ }
+ }else if(B->bPedPhysics){
+ if(A->IsVehicle())
+ return false;
+ CVector pointposA = colpoint.point - A->GetPosition();
+ speedA = A->GetSpeed(pointposA);
+
+ normalSpeedA = DotProduct(speedA, colpoint.normal);
+ normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massA = A->GetMass(pointposA, frictionDir);
+ speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * massA;
+ impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ return true;
+ }
+ }else{
+ CVector pointposA = colpoint.point - A->GetPosition();
+ CVector pointposB = colpoint.point - B->GetPosition();
+ speedA = A->GetSpeed(pointposA);
+ speedB = B->GetSpeed(pointposB);
+
+ normalSpeedA = DotProduct(speedA, colpoint.normal);
+ normalSpeedB = DotProduct(speedB, colpoint.normal);
+ vOtherSpeedA = speedA - colpoint.normal*normalSpeedA;
+ vOtherSpeedB = speedB - colpoint.normal*normalSpeedB;
+
+ fOtherSpeedA = vOtherSpeedA.Magnitude();
+ fOtherSpeedB = vOtherSpeedB.Magnitude();
+
+ frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA);
+ float massA = A->GetMass(pointposA, frictionDir);
+ float massB = B->GetMass(pointposB, frictionDir);
+ speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA);
+ if(fOtherSpeedA > speedSum){
+ impulseA = (speedSum - fOtherSpeedA) * massA;
+ impulseB = (speedSum - fOtherSpeedB) * massB;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
+ if(impulseA < -impulseLimit) impulseA = -impulseLimit;
+ if(impulseB > impulseLimit) impulseB = impulseLimit;
+ A->ApplyFrictionMoveForce(frictionDir*impulseA);
+ A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA);
+ B->ApplyFrictionMoveForce(frictionDir*impulseB);
+ B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint)
+{
+ CVector speed;
+ float normalSpeed;
+ CVector vOtherSpeed;
+ float fOtherSpeed;
+ CVector frictionDir;
+ float fImpulse;
+ float impulseLimit;
+
+ if(bPedPhysics){
+ normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal);
+ vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed;
+
+ fOtherSpeed = vOtherSpeed.Magnitude();
+ if(fOtherSpeed > 0.0f){
+ frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
+ // not really impulse but speed
+ // maybe use ApplyFrictionMoveForce instead?
+ fImpulse = -fOtherSpeed;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass;
+ if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
+ CVector vImpulse = frictionDir*fImpulse;
+ m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f);
+ return true;
+ }
+ }else{
+ CVector pointpos = colpoint.point - GetPosition();
+ speed = GetSpeed(pointpos);
+ normalSpeed = DotProduct(speed, colpoint.normal);
+ vOtherSpeed = speed - colpoint.normal*normalSpeed;
+
+ fOtherSpeed = vOtherSpeed.Magnitude();
+ if(fOtherSpeed > 0.0f){
+ frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
+ fImpulse = -fOtherSpeed * m_fMass;
+ impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5f;
+ if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
+ ApplyFrictionMoveForce(frictionDir*fImpulse);
+ ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos);
+
+ if(fOtherSpeed > 0.1f &&
+ colpoint.surfaceB != SURFACE_2 && colpoint.surfaceB != SURFACE_4 &&
+ CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){
+ CVector v = frictionDir * fOtherSpeed * 0.25f;
+ for(int i = 0; i < 4; i++)
+ CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void
+CPhysical::AddCollisionRecord(CEntity *ent)
+{
+ AddCollisionRecord_Treadable(ent);
+ this->bHasCollided = true;
+ ent->bHasCollided = true;
+ if(IsVehicle() && ent->IsVehicle()){
+ if(((CVehicle*)this)->m_nAlarmState == -1)
+ ((CVehicle*)this)->m_nAlarmState = 15000;
+ if(((CVehicle*)ent)->m_nAlarmState == -1)
+ ((CVehicle*)ent)->m_nAlarmState = 15000;
+ }
+ if(bUseCollisionRecords){
+ int i;
+ for(i = 0; i < m_nCollisionRecords; i++)
+ if(m_aCollisionRecords[i] == ent)
+ return;
+ if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS)
+ m_aCollisionRecords[m_nCollisionRecords++] = ent;
+ m_nLastTimeCollided = CTimer::GetTimeInMilliseconds();
+ }
+}
+
+void
+CPhysical::AddCollisionRecord_Treadable(CEntity *ent)
+{
+ if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){
+ CTreadable *t = (CTreadable*)ent;
+ if(t->m_nodeIndicesPeds[0] >= 0 ||
+ t->m_nodeIndicesPeds[1] >= 0 ||
+ t->m_nodeIndicesPeds[2] >= 0 ||
+ t->m_nodeIndicesPeds[3] >= 0)
+ m_pedTreadable = t;
+ if(t->m_nodeIndicesCars[0] >= 0 ||
+ t->m_nodeIndicesCars[1] >= 0 ||
+ t->m_nodeIndicesCars[2] >= 0 ||
+ t->m_nodeIndicesCars[3] >= 0)
+ m_carTreadable = t;
+ }
+}
+
+bool
+CPhysical::GetHasCollidedWith(CEntity *ent)
+{
+ int i;
+ if(bUseCollisionRecords)
+ for(i = 0; i < m_nCollisionRecords; i++)
+ if(m_aCollisionRecords[i] == ent)
+ return true;
+ return false;
+}
+
+void
+CPhysical::ProcessControl(void)
+{
+ if(!IsPed())
+ m_phy_flagA8 = false;
+ bHasContacted = false;
+ bIsInSafePosition = false;
+ bWasPostponed = false;
+ bHasHitWall = false;
+
+ if(m_status == STATUS_SIMPLE)
+ return;
+
+ m_nCollisionRecords = 0;
+ bHasCollided = false;
+ m_nCollisionPieceType = 0;
+ m_fCollisionImpulse = 0.0f;
+ m_pCollidingEntity = nil;
+
+ if(!bIsStuck){
+ if(IsObject() ||
+ IsPed() && !bPedPhysics){
+ m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f;
+ m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f;
+ float step = CTimer::GetTimeStep() * 0.003;
+ if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step &&
+ m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){
+ m_nStaticFrames++;
+ if(m_nStaticFrames > 10){
+ m_nStaticFrames = 10;
+ bIsStatic = true;
+ m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
+ m_vecMoveFriction = m_vecMoveSpeed;
+ m_vecTurnFriction = m_vecTurnSpeed;
+ return;
+ }
+ }else
+ m_nStaticFrames = 0;
+ }
+ }
+ ApplyGravity();
+ ApplyFriction();
+ ApplyAirResistance();
+}
+
+STARTPATCHES
+ InjectHook(0x4951F0, &CPhysical::Add_, PATCH_JUMP);
+ InjectHook(0x4954B0, &CPhysical::Remove_, PATCH_JUMP);
+ InjectHook(0x495540, &CPhysical::RemoveAndAdd, PATCH_JUMP);
+ InjectHook(0x495F10, &CPhysical::ProcessControl_, PATCH_JUMP);
+ InjectHook(0x4958F0, &CPhysical::AddToMovingList, PATCH_JUMP);
+ InjectHook(0x495940, &CPhysical::RemoveFromMovingList, PATCH_JUMP);
+
+ InjectHook(0x497180, &CPhysical::AddCollisionRecord, PATCH_JUMP);
+ InjectHook(0x4970C0, &CPhysical::AddCollisionRecord_Treadable, PATCH_JUMP);
+ InjectHook(0x497240, &CPhysical::GetHasCollidedWith, PATCH_JUMP);
+
+#define F3 float, float, float
+ InjectHook(0x495B10, &CPhysical::ApplyMoveSpeed, PATCH_JUMP);
+ InjectHook(0x497280, &CPhysical::ApplyTurnSpeed, PATCH_JUMP);
+ InjectHook(0x4959A0, (void (CPhysical::*)(F3))&CPhysical::ApplyMoveForce, PATCH_JUMP);
+ InjectHook(0x495A10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyTurnForce, PATCH_JUMP);
+ InjectHook(0x495D90, (void (CPhysical::*)(F3))&CPhysical::ApplyFrictionMoveForce, PATCH_JUMP);
+ InjectHook(0x495E10, (void (CPhysical::*)(F3, F3))&CPhysical::ApplyFrictionTurnForce, PATCH_JUMP);
+ InjectHook(0x499890, &CPhysical::ApplySpringCollision, PATCH_JUMP);
+ InjectHook(0x495B50, &CPhysical::ApplyGravity, PATCH_JUMP);
+ InjectHook(0x495B80, (void (CPhysical::*)(void))&CPhysical::ApplyFriction, PATCH_JUMP);
+ InjectHook(0x495C20, &CPhysical::ApplyAirResistance, PATCH_JUMP);
+
+ InjectHook(0x4973A0, &CPhysical::ApplyCollision, PATCH_JUMP);
+ InjectHook(0x4992A0, &CPhysical::ApplyCollisionAlt, PATCH_JUMP);
+ InjectHook(0x499BE0, (bool (CPhysical::*)(float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
+ InjectHook(0x49A180, (bool (CPhysical::*)(CPhysical*, float, CColPoint&))&CPhysical::ApplyFriction, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/entities/Physical.h b/src/entities/Physical.h
new file mode 100644
index 00000000..681ab5c8
--- /dev/null
+++ b/src/entities/Physical.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "Lists.h"
+#include "Entity.h"
+#include "Treadable.h"
+
+enum {
+ PHYSICAL_MAX_COLLISIONRECORDS = 6
+};
+
+class CPhysical : public CEntity
+{
+public:
+ // The not properly indented fields haven't been checked properly yet
+
+ int uAudioEntityId;
+ float unk1;
+ CTreadable *m_carTreadable;
+ CTreadable *m_pedTreadable;
+ uint32 m_nLastTimeCollided;
+ CVector m_vecMoveSpeed; // velocity
+ CVector m_vecTurnSpeed; // angular velocity
+ CVector m_vecMoveFriction;
+ CVector m_vecTurnFriction;
+ CVector m_vecMoveSpeedAvg;
+ CVector m_vecTurnSpeedAvg;
+ float m_fMass;
+ float m_fTurnMass; // moment of inertia
+ float fForceMultiplier;
+ float m_fAirResistance;
+ float m_fElasticity;
+ float fPercentSubmerged;
+ CVector m_vecCentreOfMass;
+ CEntryInfoList m_entryInfoList;
+ CPtrNode *m_movingListNode;
+
+ char field_EC;
+ uint8 m_nStaticFrames;
+ uint8 m_nCollisionRecords;
+ char field_EF;
+ CEntity *m_aCollisionRecords[PHYSICAL_MAX_COLLISIONRECORDS];
+
+ float m_fDistanceTravelled;
+
+ // damaged piece
+ float m_fCollisionImpulse;
+ CEntity *m_pCollidingEntity;
+ CVector m_vecCollisionDirection;
+ int16 m_nCollisionPieceType;
+
+ uint8 m_phy_flagA1 : 1;
+ uint8 bAffectedByGravity : 1;
+ uint8 bInfiniteMass : 1;
+ uint8 m_phy_flagA8 : 1;
+ uint8 m_phy_flagA10 : 1;
+ uint8 m_phy_flagA20 : 1;
+ uint8 m_phy_flagA40 : 1;
+ uint8 m_phy_flagA80 : 1;
+
+ uint8 m_phy_flagB1 : 1;
+ uint8 m_phy_flagB2 : 1;
+ uint8 m_phy_flagB4 : 1;
+ uint8 m_phy_flagB8 : 1;
+ uint8 m_phy_flagB10 : 1;
+ uint8 m_phy_flagB20 : 1;
+ uint8 m_phy_flagB40 : 1;
+ uint8 m_phy_flagB80 : 1;
+
+ char byteLastCollType;
+ char byteZoneLevel;
+ int16 pad;
+
+
+ // from CEntity
+ void Add(void);
+ void Remove(void);
+ CRect GetBoundRect(void);
+ void ProcessControl(void);
+
+ void RemoveAndAdd(void);
+ void AddToMovingList(void);
+ void RemoveFromMovingList(void);
+
+ // get speed of point p relative to entity center
+ CVector GetSpeed(const CVector &r);
+ CVector GetSpeed(void) { return GetSpeed(CVector(0.0f, 0.0f, 0.0f)); }
+ float GetMass(const CVector &pos, const CVector &dir) {
+ return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/m_fTurnMass +
+ 1.0f/m_fMass);
+ }
+ float GetMassTime(const CVector &pos, const CVector &dir, float t) {
+ return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/(m_fTurnMass*t) +
+ 1.0f/(m_fMass*t));
+ }
+ void UnsetIsInSafePosition(void) {
+ m_vecMoveSpeed *= -1.0f;
+ m_vecTurnSpeed *= -1.0f;
+ ApplyTurnSpeed();
+ ApplyMoveSpeed();
+ m_vecMoveSpeed *= -1.0f;
+ m_vecTurnSpeed *= -1.0f;
+ bIsInSafePosition = false;
+ }
+
+ void ApplyMoveSpeed(void);
+ void ApplyTurnSpeed(void);
+ // Force actually means Impulse here
+ void ApplyMoveForce(float jx, float jy, float jz);
+ void ApplyMoveForce(const CVector &j) { ApplyMoveForce(j.x, j.y, j.z); }
+ // v(x,y,z) is direction of force, p(x,y,z) is point relative to model center where force is applied
+ void ApplyTurnForce(float jx, float jy, float jz, float rx, float ry, float rz);
+ // v is direction of force, p is point relative to model center where force is applied
+ void ApplyTurnForce(const CVector &j, const CVector &p) { ApplyTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); }
+ void ApplyFrictionMoveForce(float jx, float jy, float jz);
+ void ApplyFrictionMoveForce(const CVector &j) { ApplyFrictionMoveForce(j.x, j.y, j.z); }
+ void ApplyFrictionTurnForce(float jx, float jy, float jz, float rx, float ry, float rz);
+ void ApplyFrictionTurnForce(const CVector &j, const CVector &p) { ApplyFrictionTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); }
+ void ApplySpringCollision(float f1, CVector &v, CVector &p, float f2, float f3);
+ void ApplyGravity(void);
+ void ApplyFriction(void);
+ void ApplyAirResistance(void);
+ bool ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB);
+ bool ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed);
+ bool ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint);
+ bool ApplyFriction(float adhesiveLimit, CColPoint &colpoint);
+
+ void AddCollisionRecord(CEntity *ent);
+ void AddCollisionRecord_Treadable(CEntity *ent);
+ bool GetHasCollidedWith(CEntity *ent);
+
+ // to make patching virtual functions possible
+ void Add_(void) { CPhysical::Add(); }
+ void Remove_(void) { CPhysical::Remove(); }
+ CRect GetBoundRect_(void) { return CPhysical::GetBoundRect(); }
+ void ProcessControl_(void) { CPhysical::ProcessControl(); }
+};
+static_assert(sizeof(CPhysical) == 0x128, "CPhysical: error");
diff --git a/src/entities/Treadable.cpp b/src/entities/Treadable.cpp
new file mode 100644
index 00000000..e2eca36a
--- /dev/null
+++ b/src/entities/Treadable.cpp
@@ -0,0 +1,7 @@
+#include "common.h"
+#include "rpworld.h"
+#include "Treadable.h"
+#include "Pools.h"
+
+void *CTreadable::operator new(size_t sz) { return CPools::GetTreadablePool()->New(); }
+void CTreadable::operator delete(void *p, size_t sz) { CPools::GetTreadablePool()->Delete((CTreadable*)p); }
diff --git a/src/entities/Treadable.h b/src/entities/Treadable.h
new file mode 100644
index 00000000..df5c9ee0
--- /dev/null
+++ b/src/entities/Treadable.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "Building.h"
+
+class CTreadable : public CBuilding
+{
+public:
+ static void *operator new(size_t);
+ static void operator delete(void*, size_t);
+
+ int16 m_nodeIndicesCars[12];
+ int16 m_nodeIndicesPeds[12];
+
+ virtual bool GetIsATreadable(void) { return true; }
+};
+static_assert(sizeof(CTreadable) == 0x94, "CTreadable: error");
diff --git a/src/entities/Vehicle.h b/src/entities/Vehicle.h
new file mode 100644
index 00000000..598b4a57
--- /dev/null
+++ b/src/entities/Vehicle.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "Physical.h"
+
+class CPed;
+
+class CVehicle : public CPhysical
+{
+public:
+ // 0x128
+ uint8 stuff1[120];
+ int16 m_nAlarmState;
+ CPed *pDriver;
+ CPed *pPassengers[8];
+ uint8 stuff2[24];
+ CEntity *m_pCurSurface;
+ uint8 stuff3[160];
+ int32 m_vehType;
+};
+static_assert(sizeof(CVehicle) == 0x288, "CVehicle: error");
+static_assert(offsetof(CVehicle, m_pCurSurface) == 0x1E0, "CVehicle: error");
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 00000000..b17a30a3
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,98 @@
+#include "common.h"
+#include <Windows.h>
+#include "patcher.h"
+#include "Renderer.h"
+#include "debugmenu_public.h"
+
+void **rwengine = *(void***)0x5A10E1;
+
+RsGlobalType &RsGlobal = *(RsGlobalType*)0x8F4360;
+
+GlobalScene &Scene = *(GlobalScene*)0x726768;
+
+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); }
+
+// Use our own implementation of rand, stolen from PS2
+
+unsigned __int64 myrand_seed = 1;
+
+int
+myrand(void)
+{
+ myrand_seed = 0x5851F42D4C957F2D * myrand_seed + 1;
+ return ((myrand_seed >> 32) & 0x7FFFFFFF);
+}
+
+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)
+{
+ if(GetAsyncKeyState('D') & 0x8000)
+ return open_script_orig("main_d.scm", mode);
+// if(GetAsyncKeyState('R') & 0x8000)
+ return open_script_orig("main_freeroam.scm", mode);
+ return open_script_orig(path, mode);
+}
+
+int (*RsEventHandler_orig)(int a, int b);
+int
+delayedPatches10(int a, int b)
+{
+ if(DebugMenuLoad()){
+ 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);
+ }
+
+ return RsEventHandler_orig(a, b);
+}
+
+void
+patch()
+{
+ StaticPatcher::Apply();
+
+ Patch<float>(0x46BC61+6, 1.0f); // car distance
+ InjectHook(0x59E460, printf, 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/math/Matrix.h b/src/math/Matrix.h
new file mode 100644
index 00000000..cc15da09
--- /dev/null
+++ b/src/math/Matrix.h
@@ -0,0 +1,245 @@
+#pragma once
+
+class CMatrix
+{
+public:
+ RwMatrix m_matrix;
+ RwMatrix *m_attachment;
+ bool m_hasRwMatrix; // are we the owner?
+
+ CMatrix(void){
+ m_attachment = nil;
+ m_hasRwMatrix = false;
+ }
+ CMatrix(CMatrix const &m){
+ m_attachment = nil;
+ m_hasRwMatrix = false;
+ *this = m;
+ }
+ CMatrix(RwMatrix *matrix, bool attach){
+ m_attachment = nil;
+ Attach(matrix, attach);
+ }
+ ~CMatrix(void){
+ if(m_hasRwMatrix && m_attachment)
+ RwMatrixDestroy(m_attachment);
+ }
+ void Attach(RwMatrix *matrix, bool attach){
+ if(m_hasRwMatrix && m_attachment)
+ RwMatrixDestroy(m_attachment);
+ m_attachment = matrix;
+ m_hasRwMatrix = attach;
+ Update();
+ }
+ void AttachRW(RwMatrix *matrix, bool attach){
+ if(m_hasRwMatrix && m_attachment)
+ RwMatrixDestroy(m_attachment);
+ m_attachment = matrix;
+ m_hasRwMatrix = attach;
+ UpdateRW();
+ }
+ void Detach(void){
+ if(m_hasRwMatrix && m_attachment)
+ RwMatrixDestroy(m_attachment);
+ m_attachment = nil;
+ }
+ void Update(void){
+ m_matrix = *m_attachment;
+ }
+ void UpdateRW(void){
+ if(m_attachment){
+ *m_attachment = m_matrix;
+ RwMatrixUpdate(m_attachment);
+ }
+ }
+ void operator=(CMatrix const &rhs){
+ m_matrix = rhs.m_matrix;
+ if(m_attachment)
+ UpdateRW();
+ }
+
+ CVector *GetPosition(void){ return (CVector*)&m_matrix.pos; }
+ CVector *GetRight(void) { return (CVector*)&m_matrix.right; }
+ CVector *GetForward(void) { return (CVector*)&m_matrix.up; }
+ CVector *GetUp(void) { return (CVector*)&m_matrix.at; }
+ void SetScale(float s){
+ m_matrix.right.x = s;
+ m_matrix.right.y = 0.0f;
+ m_matrix.right.z = 0.0f;
+
+ m_matrix.up.x = 0.0f;
+ m_matrix.up.y = s;
+ m_matrix.up.z = 0.0f;
+
+ m_matrix.at.x = 0.0f;
+ m_matrix.at.y = 0.0f;
+ m_matrix.at.z = s;
+
+ m_matrix.pos.x = 0.0f;
+ m_matrix.pos.y = 0.0f;
+ m_matrix.pos.z = 0.0f;
+ }
+ void SetRotateXOnly(float angle){
+ float c = cos(angle);
+ float s = sin(angle);
+
+ m_matrix.right.x = 1.0f;
+ m_matrix.right.y = 0.0f;
+ m_matrix.right.z = 0.0f;
+
+ m_matrix.up.x = 0.0f;
+ m_matrix.up.y = c;
+ m_matrix.up.z = s;
+
+ m_matrix.at.x = 0.0f;
+ m_matrix.at.y = -s;
+ m_matrix.at.z = c;
+ }
+ void SetRotateX(float angle){
+ SetRotateXOnly(angle);
+ m_matrix.pos.x = 0.0f;
+ m_matrix.pos.y = 0.0f;
+ m_matrix.pos.z = 0.0f;
+ }
+ void SetRotateYOnly(float angle){
+ float c = cos(angle);
+ float s = sin(angle);
+
+ m_matrix.right.x = c;
+ m_matrix.right.y = 0.0f;
+ m_matrix.right.z = -s;
+
+ m_matrix.up.x = 0.0f;
+ m_matrix.up.y = 1.0f;
+ m_matrix.up.z = 0.0f;
+
+ m_matrix.at.x = s;
+ m_matrix.at.y = 0.0f;
+ m_matrix.at.z = c;
+ }
+ void SetRotateY(float angle){
+ SetRotateYOnly(angle);
+ m_matrix.pos.x = 0.0f;
+ m_matrix.pos.y = 0.0f;
+ m_matrix.pos.z = 0.0f;
+ }
+ void SetRotateZOnly(float angle){
+ float c = cos(angle);
+ float s = sin(angle);
+
+ m_matrix.right.x = c;
+ m_matrix.right.y = s;
+ m_matrix.right.z = 0.0f;
+
+ m_matrix.up.x = -s;
+ m_matrix.up.y = c;
+ m_matrix.up.z = 0.0f;
+
+ m_matrix.at.x = 0.0f;
+ m_matrix.at.y = 0.0f;
+ m_matrix.at.z = 1.0f;
+ }
+ void SetRotateZ(float angle){
+ SetRotateZOnly(angle);
+ m_matrix.pos.x = 0.0f;
+ m_matrix.pos.y = 0.0f;
+ m_matrix.pos.z = 0.0f;
+ }
+ void Reorthogonalise(void){
+ CVector &r = *GetRight();
+ CVector &f = *GetForward();
+ CVector &u = *GetUp();
+ u = CrossProduct(r, f);
+ u.Normalise();
+ r = CrossProduct(f, u);
+ r.Normalise();
+ f = CrossProduct(u, r);
+ }
+};
+
+inline CMatrix&
+Invert(const CMatrix &src, CMatrix &dst)
+{
+ // GTA handles this as a raw 4x4 orthonormal matrix
+ // and trashes the RW flags, let's not do that
+ // actual copy of librw code:
+ RwMatrix *d = &dst.m_matrix;
+ const RwMatrix *s = &src.m_matrix;
+ d->right.x = s->right.x;
+ d->right.y = s->up.x;
+ d->right.z = s->at.x;
+ d->up.x = s->right.y;
+ d->up.y = s->up.y;
+ d->up.z = s->at.y;
+ d->at.x = s->right.z;
+ d->at.y = s->up.z;
+ d->at.z = s->at.z;
+ d->pos.x = -(s->pos.x*s->right.x +
+ s->pos.y*s->right.y +
+ s->pos.z*s->right.z);
+ d->pos.y = -(s->pos.x*s->up.x +
+ s->pos.y*s->up.y +
+ s->pos.z*s->up.z);
+ d->pos.z = -(s->pos.x*s->at.x +
+ s->pos.y*s->at.y +
+ s->pos.z*s->at.z);
+ d->flags = rwMATRIXTYPEORTHONORMAL;
+ return dst;
+}
+
+inline CMatrix
+Invert(const CMatrix &matrix)
+{
+ CMatrix inv;
+ return Invert(matrix, inv);
+}
+
+inline CVector
+operator*(const CMatrix &mat, const CVector &vec)
+{
+ return CVector(
+ mat.m_matrix.right.x * vec.x + mat.m_matrix.up.x * vec.y + mat.m_matrix.at.x * vec.z + mat.m_matrix.pos.x,
+ mat.m_matrix.right.y * vec.x + mat.m_matrix.up.y * vec.y + mat.m_matrix.at.y * vec.z + mat.m_matrix.pos.y,
+ mat.m_matrix.right.z * vec.x + mat.m_matrix.up.z * vec.y + mat.m_matrix.at.z * vec.z + mat.m_matrix.pos.z);
+}
+
+inline CMatrix
+operator*(const CMatrix &m1, const CMatrix &m2)
+{
+ CMatrix out;
+ RwMatrix *dst = &out.m_matrix;
+ const RwMatrix *src1 = &m1.m_matrix;
+ const RwMatrix *src2 = &m2.m_matrix;
+ dst->right.x = src1->right.x*src2->right.x + src1->up.x*src2->right.y + src1->at.x*src2->right.z;
+ dst->right.y = src1->right.y*src2->right.x + src1->up.y*src2->right.y + src1->at.y*src2->right.z;
+ dst->right.z = src1->right.z*src2->right.x + src1->up.z*src2->right.y + src1->at.z*src2->right.z;
+ dst->up.x = src1->right.x*src2->up.x + src1->up.x*src2->up.y + src1->at.x*src2->up.z;
+ dst->up.y = src1->right.y*src2->up.x + src1->up.y*src2->up.y + src1->at.y*src2->up.z;
+ dst->up.z = src1->right.z*src2->up.x + src1->up.z*src2->up.y + src1->at.z*src2->up.z;
+ dst->at.x = src1->right.x*src2->at.x + src1->up.x*src2->at.y + src1->at.x*src2->at.z;
+ dst->at.y = src1->right.y*src2->at.x + src1->up.y*src2->at.y + src1->at.y*src2->at.z;
+ dst->at.z = src1->right.z*src2->at.x + src1->up.z*src2->at.y + src1->at.z*src2->at.z;
+ dst->pos.x = src1->right.x*src2->pos.x + src1->up.x*src2->pos.y + src1->at.x*src2->pos.z + src1->pos.x;
+ dst->pos.y = src1->right.y*src2->pos.x + src1->up.y*src2->pos.y + src1->at.y*src2->pos.z + src1->pos.y;
+ dst->pos.z = src1->right.z*src2->pos.x + src1->up.z*src2->pos.y + src1->at.z*src2->pos.z + src1->pos.z;
+ return out;
+}
+
+inline CVector
+MultiplyInverse(const CMatrix &mat, const CVector &vec)
+{
+ CVector v(vec.x - mat.m_matrix.pos.x, vec.y - mat.m_matrix.pos.y, vec.z - mat.m_matrix.pos.z);
+ return CVector(
+ mat.m_matrix.right.x * v.x + mat.m_matrix.right.y * v.y + mat.m_matrix.right.z * v.z,
+ mat.m_matrix.up.x * v.x + mat.m_matrix.up.y * v.y + mat.m_matrix.up.z * v.z,
+ mat.m_matrix.at.x * v.x + mat.m_matrix.at.y * v.y + mat.m_matrix.at.z * v.z);
+}
+
+inline CVector
+Multiply3x3(const CMatrix &mat, const CVector &vec)
+{
+ return CVector(
+ mat.m_matrix.right.x * vec.x + mat.m_matrix.up.x * vec.y + mat.m_matrix.at.x * vec.z,
+ mat.m_matrix.right.y * vec.x + mat.m_matrix.up.y * vec.y + mat.m_matrix.at.y * vec.z,
+ mat.m_matrix.right.z * vec.x + mat.m_matrix.up.z * vec.y + mat.m_matrix.at.z * vec.z);
+}
diff --git a/src/math/Rect.h b/src/math/Rect.h
new file mode 100644
index 00000000..212645fa
--- /dev/null
+++ b/src/math/Rect.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#pragma once
+
+class CRect
+{
+public:
+ float left; // x min
+ float top; // y max
+ float right; // x max
+ float bottom; // y min
+
+ CRect(void){
+ left = 1000000.0f;
+ bottom = 1000000.0f;
+ right = -1000000.0f;
+ top = -1000000.0f;
+ }
+ CRect(float l, float b, float r, float t){
+ left = l;
+ bottom = b;
+ right = r;
+ top = t;
+ }
+ void ContainPoint(CVector const &v){
+ if(v.x < left) left = v.x;
+ if(v.x > right) right = v.x;
+ if(v.y < bottom) bottom = v.y;
+ if(v.y > top) top = v.y;
+ }
+};
diff --git a/src/math/Vector.h b/src/math/Vector.h
new file mode 100644
index 00000000..98c3f0ec
--- /dev/null
+++ b/src/math/Vector.h
@@ -0,0 +1,82 @@
+#pragma once
+
+class CVector
+{
+public:
+ float x, y, z;
+ CVector(void) {}
+ CVector(float x, float y, float z) : x(x), y(y), z(z) {}
+// CVector(rw::V3d const &v) : x(v.x), y(v.y), z(v.z) {}
+ float Magnitude(void) const { return sqrt(x*x + y*y + z*z); }
+ float MagnitudeSqr(void) const { return x*x + y*y + z*z; }
+ float Magnitude2D(void) const { return sqrt(x*x + y*y); }
+ void Normalise(void){
+ float sq = MagnitudeSqr();
+ if(sq > 0.0f){
+ float invsqrt = 1.0f/sqrt(sq);
+ x *= invsqrt;
+ y *= invsqrt;
+ z *= invsqrt;
+ }else
+ x = 1.0f;
+ }
+// rw::V3d ToRW(void){
+// return rw::makeV3d(x, y, z);
+// }
+// void operator=(rw::V3d const &rhs){
+// x = rhs.x;
+// y = rhs.y;
+// z = rhs.z;
+// }
+ CVector operator-(const CVector &rhs) const {
+ return CVector(x-rhs.x, y-rhs.y, z-rhs.z);
+ }
+ CVector operator+(const CVector &rhs) const {
+ return CVector(x+rhs.x, y+rhs.y, z+rhs.z);
+ }
+ CVector operator*(float t) const {
+ return CVector(x*t, y*t, z*t);
+ }
+ CVector operator/(float t) const {
+ return CVector(x/t, y/t, z/t);
+ }
+ CVector &operator-=(const CVector &rhs) {
+ this->x -= rhs.x;
+ this->y -= rhs.y;
+ this->z -= rhs.z;
+ return *this;
+ }
+ CVector &operator+=(const CVector &rhs) {
+ this->x += rhs.x;
+ this->y += rhs.y;
+ this->z += rhs.z;
+ return *this;
+ }
+ CVector &operator*=(float t) {
+ this->x *= t;
+ this->y *= t;
+ this->z *= t;
+ return *this;
+ }
+ CVector &operator/=(float t) {
+ this->x /= t;
+ this->y /= t;
+ this->z /= t;
+ return *this;
+ }
+};
+
+inline float
+DotProduct(const CVector &v1, const CVector &v2)
+{
+ return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
+}
+
+inline CVector
+CrossProduct(const CVector &v1, const CVector &v2)
+{
+ return CVector(
+ v1.y*v2.z - v1.z*v2.y,
+ v1.z*v2.x - v1.x*v2.z,
+ v1.x*v2.y - v1.y*v2.x);
+}
diff --git a/src/math/Vector2D.h b/src/math/Vector2D.h
new file mode 100644
index 00000000..3c0013d4
--- /dev/null
+++ b/src/math/Vector2D.h
@@ -0,0 +1,37 @@
+#pragma once
+
+class CVector2D
+{
+public:
+ float x, y;
+ CVector2D(void) {}
+ CVector2D(float x, float y) : x(x), y(y) {}
+ CVector2D(const CVector &v) : x(v.x), y(v.y) {}
+ float Magnitude(void) const { return sqrt(x*x + y*y); }
+ float MagnitudeSqr(void) const { return x*x + y*y; }
+
+ void Normalise(void){
+ float sq = MagnitudeSqr();
+ if(sq > 0.0f){
+ float invsqrt = 1.0f/sqrt(sq);
+ x *= invsqrt;
+ y *= invsqrt;
+ }else
+ x = 0.0f;
+ }
+ CVector2D operator-(const CVector2D &rhs) const {
+ return CVector2D(x-rhs.x, y-rhs.y);
+ }
+ CVector2D operator+(const CVector2D &rhs) const {
+ return CVector2D(x+rhs.x, y+rhs.y);
+ }
+ CVector2D operator*(float t) const {
+ return CVector2D(x*t, y*t);
+ }
+};
+
+inline float
+CrossProduct2D(const CVector2D &v1, const CVector2D &v2)
+{
+ return v1.x*v2.y - v1.y*v2.x;
+}
diff --git a/src/modelinfo/BaseModelInfo.cpp b/src/modelinfo/BaseModelInfo.cpp
new file mode 100644
index 00000000..f44c86b6
--- /dev/null
+++ b/src/modelinfo/BaseModelInfo.cpp
@@ -0,0 +1,117 @@
+#include "common.h"
+#include "patcher.h"
+#include "templates.h"
+#include "TxdStore.h"
+#include "2dEffect.h"
+#include "BaseModelInfo.h"
+
+
+CBaseModelInfo::CBaseModelInfo(ModeInfoType type)
+{
+ m_colModel = nil;
+ m_twodEffects = 0;
+ m_objectId = -1;
+ m_refCount = 0;
+ m_txdSlot = -1;
+ m_type = type;
+ m_num2dEffects = 0;
+ m_freeCol = false;
+}
+
+void
+CBaseModelInfo::Shutdown(void)
+{
+ DeleteCollisionModel();
+ DeleteRwObject();
+ m_twodEffects = 0;
+ m_num2dEffects = 0;
+ m_txdSlot = -1;
+}
+
+void
+CBaseModelInfo::DeleteCollisionModel(void)
+{
+ if(m_colModel && m_freeCol){
+ if(m_colModel)
+ delete m_colModel;
+ m_colModel = nil;
+ }
+}
+
+void
+CBaseModelInfo::AddRef(void)
+{
+ m_refCount++;
+ AddTexDictionaryRef();
+}
+
+void
+CBaseModelInfo::RemoveRef(void)
+{
+ m_refCount--;
+ RemoveTexDictionaryRef();
+}
+
+void
+CBaseModelInfo::SetTexDictionary(const char *name)
+{
+ int slot = CTxdStore::FindTxdSlot(name);
+ if(slot < 0)
+ slot = CTxdStore::AddTxdSlot(name);
+ m_txdSlot = slot;
+}
+
+void
+CBaseModelInfo::AddTexDictionaryRef(void)
+{
+ CTxdStore::AddRef(m_txdSlot);
+}
+
+void
+CBaseModelInfo::RemoveTexDictionaryRef(void)
+{
+ CTxdStore::RemoveRef(m_txdSlot);
+}
+
+void
+CBaseModelInfo::Init2dEffects(void)
+{
+ m_twodEffects = nil;
+ m_num2dEffects = 0;
+}
+
+void
+CBaseModelInfo::Add2dEffect(C2dEffect *fx)
+{
+ if(m_twodEffects)
+ m_num2dEffects++;
+ else{
+ m_twodEffects = fx;
+ m_num2dEffects = 1;
+ }
+}
+
+C2dEffect*
+CBaseModelInfo::Get2dEffect(int n)
+{
+ if(m_twodEffects)
+ return &m_twodEffects[n];
+ else
+ return nil;
+}
+
+
+STARTPATCHES
+ // can't easily replace ctor at 4F6A50
+ InjectHook(0x4F6A90, &CBaseModelInfo::Shutdown_, PATCH_JUMP);
+ InjectHook(0x4F6AC0, &CBaseModelInfo::DeleteCollisionModel, PATCH_JUMP);
+ InjectHook(0x4F6B70, &CBaseModelInfo::ClearTexDictionary, PATCH_JUMP);
+ InjectHook(0x4F6BA0, &CBaseModelInfo::AddRef, PATCH_JUMP);
+ InjectHook(0x4F6BB0, &CBaseModelInfo::RemoveRef, PATCH_JUMP);
+ InjectHook(0x4F6B40, &CBaseModelInfo::SetTexDictionary, PATCH_JUMP);
+ InjectHook(0x4F6B80, &CBaseModelInfo::AddTexDictionaryRef, PATCH_JUMP);
+ InjectHook(0x4F6B90, &CBaseModelInfo::RemoveTexDictionaryRef, PATCH_JUMP);
+ InjectHook(0x4F6B20, &CBaseModelInfo::Add2dEffect, PATCH_JUMP);
+ InjectHook(0x4F6AF0, &CBaseModelInfo::Init2dEffects, PATCH_JUMP);
+ InjectHook(0x4F6B00, &CBaseModelInfo::Get2dEffect, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/BaseModelInfo.h b/src/modelinfo/BaseModelInfo.h
new file mode 100644
index 00000000..fadea18a
--- /dev/null
+++ b/src/modelinfo/BaseModelInfo.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "Collision.h"
+
+enum ModeInfoType : uint8
+{
+ MITYPE_NA = 0,
+ MITYPE_SIMPLE = 1,
+ MITYPE_MLO = 2,
+ MITYPE_TIME = 3,
+ MITYPE_CLUMP = 4,
+ MITYPE_VEHICLE = 5,
+ MITYPE_PED = 6,
+ MITYPE_XTRACOMPS = 7,
+};
+static_assert(sizeof(ModeInfoType) == 1, "ModeInfoType: error");
+
+class C2dEffect;
+
+class CBaseModelInfo
+{
+protected:
+ // TODO?: make more things protected
+ char m_name[24];
+ CColModel *m_colModel;
+ C2dEffect *m_twodEffects;
+ int16 m_objectId;
+public:
+ uint16 m_refCount;
+ int16 m_txdSlot;
+ ModeInfoType m_type;
+ uint8 m_num2dEffects;
+ bool m_freeCol;
+
+ CBaseModelInfo(ModeInfoType type);
+ virtual ~CBaseModelInfo() {}
+ virtual void Shutdown(void);
+ virtual void DeleteRwObject(void) = 0;
+ virtual RwObject *CreateInstance(RwMatrix *) = 0;
+ virtual RwObject *CreateInstance(void) = 0;
+ virtual RwObject *GetRwObject(void) = 0;
+
+ bool IsSimple(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME; }
+ char *GetName(void) { return m_name; }
+ void SetName(const char *name) { strncpy(m_name, name, 24); }
+ void SetColModel(CColModel *col, bool free = false){
+ m_colModel = col; m_freeCol = free; }
+ CColModel *GetColModel(void) { return m_colModel; }
+ void DeleteCollisionModel(void);
+ void ClearTexDictionary(void) { m_txdSlot = -1; }
+ short GetObjectID(void) { return m_objectId; }
+ void SetObjectID(short id) { m_objectId = id; }
+ short GetTxdSlot(void) { return m_txdSlot; }
+ void AddRef(void);
+ void RemoveRef(void);
+ void SetTexDictionary(const char *name);
+ void AddTexDictionaryRef(void);
+ void RemoveTexDictionaryRef(void);
+ void Init2dEffects(void);
+ void Add2dEffect(C2dEffect *fx);
+ C2dEffect *Get2dEffect(int n);
+
+ void Shutdown_(void) { this->CBaseModelInfo::Shutdown(); }
+};
+
+static_assert(sizeof(CBaseModelInfo) == 0x30, "CBaseModelInfo: error");
diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp
new file mode 100644
index 00000000..4a19f1df
--- /dev/null
+++ b/src/modelinfo/ClumpModelInfo.cpp
@@ -0,0 +1,156 @@
+#include "common.h"
+#include "patcher.h"
+#include "NodeName.h"
+#include "VisibilityPlugins.h"
+#include "ModelInfo.h"
+
+void
+CClumpModelInfo::DeleteRwObject(void)
+{
+ if(m_clump){
+ RpClumpDestroy(m_clump);
+ m_clump = nil;
+ RemoveTexDictionaryRef();
+ }
+}
+
+RwObject*
+CClumpModelInfo::CreateInstance(void)
+{
+ if(m_clump)
+ return (RwObject*)RpClumpClone(m_clump);
+ return nil;
+}
+
+RwObject*
+CClumpModelInfo::CreateInstance(RwMatrix *m)
+{
+ if(m_clump){
+ RpClump *clump = (RpClump*)CreateInstance();
+ *RwFrameGetMatrix(RpClumpGetFrame(clump)) = *m;
+ return (RwObject*)clump;
+ }
+ return nil;
+}
+
+RpAtomic*
+CClumpModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data)
+{
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, (RpAtomicCallBackRender)data);
+ return atomic;
+}
+
+void
+CClumpModelInfo::SetClump(RpClump *clump)
+{
+ m_clump = clump;
+ CVisibilityPlugins::SetClumpModelInfo(m_clump, this);
+ AddTexDictionaryRef();
+ RpClumpForAllAtomics(clump, SetAtomicRendererCB, nil);
+ if(strncmp(GetName(), "playerh", 8) == 0)
+ RpClumpForAllAtomics(clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPlayerCB);
+}
+
+void
+CClumpModelInfo::SetFrameIds(RwObjectNameIdAssocation *assocs)
+{
+ int32 i;
+ RwObjectNameAssociation objname;
+
+ for(i = 0; assocs[i].name; i++)
+ if((assocs[i].flags & CLUMP_FLAG_NO_HIERID) == 0){
+ objname.frame = nil;
+ objname.name = assocs[i].name;
+ RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindFrameFromNameWithoutIdCB, &objname);
+ if(objname.frame)
+ CVisibilityPlugins::SetFrameHierarchyId(objname.frame, assocs[i].hierId);
+ }
+}
+
+RwFrame*
+CClumpModelInfo::FindFrameFromIdCB(RwFrame *frame, void *data)
+{
+ RwObjectIdAssociation *assoc = (RwObjectIdAssociation*)data;
+
+ if(CVisibilityPlugins::GetFrameHierarchyId(frame) != assoc->id){
+ RwFrameForAllChildren(frame, FindFrameFromIdCB, assoc);
+ return assoc->frame ? nil : frame;
+ }else{
+ assoc->frame = frame;
+ return nil;
+ }
+}
+
+RwFrame*
+CClumpModelInfo::FindFrameFromNameCB(RwFrame *frame, void *data)
+{
+ RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data;
+
+ if(_strcmpi(GetFrameNodeName(frame), assoc->name) != 0){
+ RwFrameForAllChildren(frame, FindFrameFromNameCB, assoc);
+ return assoc->frame ? nil : frame;
+ }else{
+ assoc->frame = frame;
+ return nil;
+ }
+}
+
+RwFrame*
+CClumpModelInfo::FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data)
+{
+ RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data;
+
+ if(CVisibilityPlugins::GetFrameHierarchyId(frame) ||
+ _strcmpi(GetFrameNodeName(frame), assoc->name) != 0){
+ RwFrameForAllChildren(frame, FindFrameFromNameWithoutIdCB, assoc);
+ return assoc->frame ? nil : frame;
+ }else{
+ assoc->frame = frame;
+ return nil;
+ }
+}
+
+RwFrame*
+CClumpModelInfo::FillFrameArrayCB(RwFrame *frame, void *data)
+{
+ int32 id;
+ RwFrame **frames = (RwFrame**)data;
+ id = CVisibilityPlugins::GetFrameHierarchyId(frame);
+ if(id > 0)
+ frames[id] = frame;
+ RwFrameForAllChildren(frame, FillFrameArrayCB, data);
+ return frame;
+}
+
+void
+CClumpModelInfo::FillFrameArray(RpClump *clump, RwFrame **frames)
+{
+ RwFrameForAllChildren(RpClumpGetFrame(clump), FillFrameArrayCB, frames);
+}
+
+RwFrame*
+CClumpModelInfo::GetFrameFromId(RpClump *clump, int32 id)
+{
+ RwObjectIdAssociation assoc;
+ assoc.id = id;
+ assoc.frame = nil;
+ RwFrameForAllChildren(RpClumpGetFrame(clump), FindFrameFromIdCB, &assoc);
+ return assoc.frame;
+}
+
+
+STARTPATCHES
+ InjectHook(0x4F8800, &CClumpModelInfo::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x4F8920, &CClumpModelInfo::CreateInstance_1, PATCH_JUMP);
+ InjectHook(0x4F88A0, &CClumpModelInfo::CreateInstance_2, PATCH_JUMP);
+ InjectHook(0x50C1C0, &CClumpModelInfo::GetRwObject_, PATCH_JUMP);
+ InjectHook(0x4F8830, &CClumpModelInfo::SetClump_, PATCH_JUMP);
+ InjectHook(0x4F8940, &CClumpModelInfo::SetAtomicRendererCB, PATCH_JUMP);
+ InjectHook(0x4F8960, &CClumpModelInfo::FindFrameFromNameCB, PATCH_JUMP);
+ InjectHook(0x4F8A10, &CClumpModelInfo::FindFrameFromNameWithoutIdCB, PATCH_JUMP);
+ InjectHook(0x4F8AD0, &CClumpModelInfo::FindFrameFromIdCB, PATCH_JUMP);
+ InjectHook(0x4F8BB0, &CClumpModelInfo::SetFrameIds, PATCH_JUMP);
+ InjectHook(0x4F8B20, &CClumpModelInfo::FillFrameArrayCB, PATCH_JUMP);
+ InjectHook(0x4F8B90, &CClumpModelInfo::FillFrameArray, PATCH_JUMP);
+ InjectHook(0x4F8B50, &CClumpModelInfo::GetFrameFromId, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/ClumpModelInfo.h b/src/modelinfo/ClumpModelInfo.h
new file mode 100644
index 00000000..909d241b
--- /dev/null
+++ b/src/modelinfo/ClumpModelInfo.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "BaseModelInfo.h"
+
+struct RwObjectNameIdAssocation
+{
+ char *name;
+ int32 hierId;
+ uint32 flags;
+};
+
+struct RwObjectNameAssociation
+{
+ char *name;
+ RwFrame *frame;
+};
+
+struct RwObjectIdAssociation
+{
+ int32 id;
+ RwFrame *frame;
+};
+
+enum {
+ CLUMP_FLAG_NO_HIERID = 0x1,
+};
+
+
+class CClumpModelInfo : public CBaseModelInfo
+{
+public:
+ RpClump *m_clump;
+
+ CClumpModelInfo(void) : CBaseModelInfo(MITYPE_CLUMP) {}
+ CClumpModelInfo(ModeInfoType id) : CBaseModelInfo(id) {}
+ ~CClumpModelInfo() {}
+ void DeleteRwObject(void);
+ RwObject *CreateInstance(void);
+ RwObject *CreateInstance(RwMatrix *);
+ RwObject *GetRwObject(void) { return (RwObject*)m_clump; }
+
+ virtual void SetClump(RpClump *);
+
+ static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data);
+ void SetFrameIds(RwObjectNameIdAssocation *assocs);
+ static RwFrame *FindFrameFromNameCB(RwFrame *frame, void *data);
+ static RwFrame *FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data);
+ static RwFrame *FindFrameFromIdCB(RwFrame *frame, void *data);
+ static void FillFrameArray(RpClump *clump, RwFrame **frames);
+ static RwFrame *FillFrameArrayCB(RwFrame *frame, void *data);
+ static RwFrame *GetFrameFromId(RpClump *clump, int32 id);
+
+
+ void DeleteRwObject_(void) { this->CClumpModelInfo::DeleteRwObject(); }
+ RwObject *CreateInstance_1(void) { return this->CClumpModelInfo::CreateInstance(); }
+ RwObject *CreateInstance_2(RwMatrix *m) { return this->CClumpModelInfo::CreateInstance(m); }
+ RwObject *GetRwObject_(void) { return this->CClumpModelInfo::GetRwObject(); }
+ void SetClump_(RpClump *clump) { this->CClumpModelInfo::SetClump(clump); }
+};
+static_assert(sizeof(CClumpModelInfo) == 0x34, "CClumpModelInfo: error");
diff --git a/src/modelinfo/ModelIndices.cpp b/src/modelinfo/ModelIndices.cpp
new file mode 100644
index 00000000..9a8aaead
--- /dev/null
+++ b/src/modelinfo/ModelIndices.cpp
@@ -0,0 +1,32 @@
+#include "common.h"
+#include "patcher.h"
+#include "ModelIndices.h"
+
+#define X(name, var, addr) int16 &var = *(int16*)addr;
+ MODELINDICES
+#undef X
+
+void
+InitModelIndices(void)
+{
+#define X(name, var, addr) var = -1;
+ MODELINDICES
+#undef X
+}
+
+void
+MatchModelString(const char *modelname, int16 id)
+{
+#define X(name, var, addr) \
+ if(strcmp(name, modelname) == 0){ \
+ var = id; \
+ return; \
+ }
+ MODELINDICES
+#undef X
+}
+
+STARTPATCHES
+ InjectHook(0x48EB60, InitModelIndices, PATCH_JUMP);
+ InjectHook(0x48F030, MatchModelString, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/ModelIndices.h b/src/modelinfo/ModelIndices.h
new file mode 100644
index 00000000..5de10558
--- /dev/null
+++ b/src/modelinfo/ModelIndices.h
@@ -0,0 +1,224 @@
+#define MODELINDICES \
+ X("fire_hydrant", MI_FIRE_HYDRANT, 0x5F5A00) \
+ X("bagelstnd02", MI_BAGELSTAND2, 0x5F59FC) \
+ X("fish01", MI_FISHSTALL01, 0x5F59EC) \
+ X("fishstall02", MI_FISHSTALL02, 0x5F59F0) \
+ X("fishstall03", MI_FISHSTALL03, 0x5F59F4) \
+ X("fishstall04", MI_FISHSTALL04, 0x5F59F8) \
+ X("taxisign", MI_TAXISIGN, 0x5F59E8) \
+ X("phonesign", MI_PHONESIGN, 0x5F59E4) \
+ X("noparkingsign1", MI_NOPARKINGSIGN1, 0x5F59E0) \
+ X("bussign1", MI_BUSSIGN1, 0x5F59DC) \
+ X("roadworkbarrier1", MI_ROADWORKBARRIER1, 0x5F59D8) \
+ X("dump1", MI_DUMP1, 0x5F59D4) \
+ X("trafficcone", MI_TRAFFICCONE, 0x5F59D0) \
+ X("newsstand1", MI_NEWSSTAND, 0x5F59CC) \
+ X("postbox1", MI_POSTBOX1, 0x5F59C8) \
+ X("bin1", MI_BIN, 0x5F59C4) \
+ X("wastebin", MI_WASTEBIN, 0x5F59C0) \
+ X("phonebooth1", MI_PHONEBOOTH1, 0x5F59BC) \
+ X("parkingmeter", MI_PARKINGMETER, 0x5F59B8) \
+ X("trafficlight1", MI_TRAFFICLIGHTS, 0x5F5958) \
+ X("lamppost1", MI_SINGLESTREETLIGHTS1, 0x5F595C) \
+ X("lamppost2", MI_SINGLESTREETLIGHTS2, 0x5F5960) \
+ X("lamppost3", MI_SINGLESTREETLIGHTS3, 0x5F5964) \
+ X("doublestreetlght1", MI_DOUBLESTREETLIGHTS, 0x5F5968) \
+ X("rd_Road2A10", MI_ROADSFORROADBLOCKSSTART, 0x5F596C) \
+ X("rd_Road1A30", MI_ROADSFORROADBLOCKSEND, 0x5F5970) \
+ X("veg_tree1", MI_TREE1, 0x5F5974) \
+ X("veg_tree3", MI_TREE2, 0x5F5978) \
+ X("veg_treea1", MI_TREE3, 0x5F597C) \
+ X("veg_treenew01", MI_TREE4, 0x5F5980) \
+ X("veg_treenew05", MI_TREE5, 0x5F5984) \
+ X("veg_treeb1", MI_TREE6, 0x5F5988) \
+ X("veg_treenew10", MI_TREE7, 0x5F598C) \
+ X("veg_treea3", MI_TREE8, 0x5F5990) \
+ X("veg_treenew09", MI_TREE9, 0x5F5994) \
+ X("veg_treenew08", MI_TREE10, 0x5F5998) \
+ X("veg_treenew03", MI_TREE11, 0x5F599C) \
+ X("veg_treenew16", MI_TREE12, 0x5F59A0) \
+ X("veg_treenew17", MI_TREE13, 0x5F59A4) \
+ X("veg_treenew06", MI_TREE14, 0x5F59A8) \
+ X("doc_crane_cab", MODELID_CRANE_1, 0x5F59AC) \
+ X("cranetopb", MODELID_CRANE_2, 0x5F59B0) \
+ X("cranetopa", MODELID_CRANE_3, 0x5F59B4) \
+ X("package1", MI_COLLECTABLE1, 0x5F5A04) \
+ X("Money", MI_MONEY, 0x5F5A08) \
+ X("barrel1", MI_CARMINE, 0x5F5A0C) \
+ X("oddjgaragdoor", MI_GARAGEDOOR1, 0x5F5A10) \
+ X("bombdoor", MI_GARAGEDOOR2, 0x5F5A14) \
+ X("door_bombshop", MI_GARAGEDOOR3, 0x5F5A18) \
+ X("vheistlocdoor", MI_GARAGEDOOR4, 0x5F5A1C) \
+ X("door2_garage", MI_GARAGEDOOR5, 0x5F5A20) \
+ X("ind_slidedoor", MI_GARAGEDOOR6, 0x5F5A24) \
+ X("bankjobdoor", MI_GARAGEDOOR7, 0x5F5A28) \
+ X("door_jmsgrage", MI_GARAGEDOOR9, 0x5F5A2C) \
+ X("jamesgrge_kb", MI_GARAGEDOOR10, 0x5F5A30) \
+ X("door_sfehousegrge", MI_GARAGEDOOR11, 0x5F5A34) \
+ X("shedgaragedoor", MI_GARAGEDOOR12, 0x5F5A38) \
+ X("door4_garage", MI_GARAGEDOOR13, 0x5F5A3C) \
+ X("door_col_compnd_01", MI_GARAGEDOOR14, 0x5F5A40) \
+ X("door_col_compnd_02", MI_GARAGEDOOR15, 0x5F5A44) \
+ X("door_col_compnd_03", MI_GARAGEDOOR16, 0x5F5A48) \
+ X("door_col_compnd_04", MI_GARAGEDOOR17, 0x5F5A4C) \
+ X("door_col_compnd_05", MI_GARAGEDOOR18, 0x5F5A50) \
+ X("impex_door", MI_GARAGEDOOR19, 0x5F5A54) \
+ X("SalvGarage", MI_GARAGEDOOR20, 0x5F5A58) \
+ X("door3_garage", MI_GARAGEDOOR21, 0x5F5A5C) \
+ X("leveldoor2", MI_GARAGEDOOR22, 0x5F5A60) \
+ X("double_garage_dr", MI_GARAGEDOOR23, 0x5F5A64) \
+ X("amcogaragedoor", MI_GARAGEDOOR24, 0x5F5A68) \
+ X("towergaragedoor1", MI_GARAGEDOOR25, 0x5F5A6C) \
+ X("towergaragedoor2", MI_GARAGEDOOR26, 0x5F5A70) \
+ X("towergaragedoor3", MI_GARAGEDOOR27, 0x5F5A74) \
+ X("plysve_gragedoor", MI_GARAGEDOOR28, 0x5F5A78) \
+ X("impexpsubgrgdoor", MI_GARAGEDOOR29, 0x5F5A7C) \
+ X("Sub_sprayshopdoor", MI_GARAGEDOOR30, 0x5F5A80) \
+ X("ind_plyrwoor", MI_GARAGEDOOR31, 0x5F5A84) \
+ X("8ballsuburbandoor", MI_GARAGEDOOR32, 0x5F5A88) \
+ X("barrel2", MI_NAUTICALMINE, 0x5F5A8C) \
+ X("crushercrush", MI_CRUSHERBODY, 0x5F5A90) \
+ X("crushertop", MI_CRUSHERLID, 0x5F5A94) \
+ X("donkeymag", MI_DONKEYMAG, 0x5F5A98) \
+ X("bullion", MI_BULLION, 0x5F5A9C) \
+ X("floatpackge1", MI_FLOATPACKAGE1, 0x5F5AA0) \
+ X("briefcase", MI_BRIEFCASE, 0x5F5AA4) \
+ X("chinabanner1", MI_CHINABANNER1, 0x5F5AA8) \
+ X("chinabanner2", MI_CHINABANNER2, 0x5F5AAC) \
+ X("chinabanner3", MI_CHINABANNER3, 0x5F5AB0) \
+ X("chinabanner4", MI_CHINABANNER4, 0x5F5AB4) \
+ X("iten_chinatown5", MI_CHINABANNER5, 0x5F5AB8) \
+ X("iten_chinatown7", MI_CHINABANNER6, 0x5F5ABC) \
+ X("iten_chinatown3", MI_CHINABANNER7, 0x5F5AC0) \
+ X("iten_chinatown2", MI_CHINABANNER8, 0x5F5AC4) \
+ X("iten_chinatown4", MI_CHINABANNER9, 0x5F5AC8) \
+ X("iten_washline01", MI_CHINABANNER10, 0x5F5ACC) \
+ X("iten_washline02", MI_CHINABANNER11, 0x5F5AD0) \
+ X("iten_washline03", MI_CHINABANNER12, 0x5F5AD4) \
+ X("chinalanterns", MI_CHINALANTERN, 0x5F5AD8) \
+ X("glassfx1", MI_GLASS1, 0x5F5ADC) \
+ X("glassfx2", MI_GLASS2, 0x5F5AE0) \
+ X("glassfx3", MI_GLASS3, 0x5F5AE4) \
+ X("glassfx4", MI_GLASS4, 0x5F5AE8) \
+ X("glassfx55", MI_GLASS5, 0x5F5AEC) \
+ X("glassfxsub1", MI_GLASS6, 0x5F5AF0) \
+ X("glassfxsub2", MI_GLASS7, 0x5F5AF4) \
+ X("glassfx_composh", MI_GLASS8, 0x5F5AF8) \
+ X("bridge_liftsec", MI_BRIDGELIFT, 0x5F5AFC) \
+ X("bridge_liftweight", MI_BRIDGEWEIGHT, 0x5F5B00) \
+ X("subbridge_lift", MI_BRIDGEROADSEGMENT, 0x5F5B04) \
+ X("barrel4", MI_EXPLODINGBARREL, 0x5F5B08) \
+ X("flagsitaly", MI_ITALYBANNER1, 0x5F5B0C) \
+ X("adrenaline", MI_PICKUP_ADRENALINE, 0x5F5B10) \
+ X("bodyarmour", MI_PICKUP_BODYARMOUR, 0x5F5B14) \
+ X("info", MI_PICKUP_INFO, 0x5F5B18) \
+ X("health", MI_PICKUP_HEALTH, 0x5F5B1C) \
+ X("bonus", MI_PICKUP_BONUS, 0x5F5B20) \
+ X("bribe", MI_PICKUP_BRIBE, 0x5F5B24) \
+ X("killfrenzy", MI_PICKUP_KILLFRENZY, 0x5F5B28) \
+ X("camerapickup", MI_PICKUP_CAMERA, 0x5F5B2C) \
+ X("bollardlight", MI_BOLLARDLIGHT, 0x5F5B30) \
+ X("magnet", MI_MAGNET, 0x5F5B34) \
+ X("streetlamp1", MI_STREETLAMP1, 0x5F5B38) \
+ X("streetlamp2", MI_STREETLAMP2, 0x5F5B3C) \
+ X("railtrax_lo4b", MI_RAILTRACKS, 0x5F5B40) \
+ X("bar_barrier10", MI_FENCE, 0x5F5B44) \
+ X("bar_barrier12", MI_FENCE2, 0x5F5B48) \
+ X("petrolpump", MI_PETROLPUMP, 0x5F5B4C) \
+ X("bodycast", MI_BODYCAST, 0x5F5B50) \
+ X("backdoor", MI_BACKDOOR, 0x5F5B54) \
+ X("coffee", MI_COFFEE, 0x5F5B58) \
+ X("bouy", MI_BUOY, 0x5F5B5C) \
+ X("parktable1", MI_PARKTABLE, 0x5F5B60) \
+ X("sbwy_tunl_start", MI_SUBWAY1, 0x5F5B64) \
+ X("sbwy_tunl_bit", MI_SUBWAY2, 0x5F5B68) \
+ X("sbwy_tunl_bend", MI_SUBWAY3, 0x5F5B6C) \
+ X("sbwy_tunl_cstm6", MI_SUBWAY4, 0x5F5B70) \
+ X("sbwy_tunl_cstm7", MI_SUBWAY5, 0x5F5B74) \
+ X("sbwy_tunl_cstm8", MI_SUBWAY6, 0x5F5B78) \
+ X("sbwy_tunl_cstm10", MI_SUBWAY7, 0x5F5B7C) \
+ X("sbwy_tunl_cstm9", MI_SUBWAY8, 0x5F5B80) \
+ X("sbwy_tunl_cstm11", MI_SUBWAY9, 0x5F5B84) \
+ X("sbwy_tunl_cstm1", MI_SUBWAY10, 0x5F5B88) \
+ X("sbwy_tunl_cstm2", MI_SUBWAY11, 0x5F5B8C) \
+ X("sbwy_tunl_cstm4", MI_SUBWAY12, 0x5F5B90) \
+ X("sbwy_tunl_cstm3", MI_SUBWAY13, 0x5F5B94) \
+ X("sbwy_tunl_cstm5", MI_SUBWAY14, 0x5F5B98) \
+ X("subplatform_n2", MI_SUBWAY15, 0x5F5B9C) \
+ X("suby_tunl_start", MI_SUBWAY16, 0x5F5BA0) \
+ X("sbwy_tunl_start2", MI_SUBWAY17, 0x5F5BA4) \
+ X("indy_tunl_start", MI_SUBWAY18, 0x5F5BA8) \
+ X("indsubway03", MI_SUBPLATFORM_IND, 0x5F5BAC) \
+ X("comerside_subway", MI_SUBPLATFORM_COMS, 0x5F5BB0) \
+ X("subplatform", MI_SUBPLATFORM_COMS2, 0x5F5BB4) \
+ X("subplatform_n", MI_SUBPLATFORM_COMN, 0x5F5BB8) \
+ X("Otherside_subway", MI_SUBPLATFORM_SUB, 0x5F5BBC) \
+ X("subplatform_sub", MI_SUBPLATFORM_SUB2, 0x5F5BC0) \
+ X("files", MI_FILES, 0x5F5BC4)
+
+#define X(name, var, addr) extern int16 &var;
+ MODELINDICES
+#undef X
+
+// and some hardcoded ones
+// expand as needed
+enum
+{
+ MI_COP = 1,
+ MI_SWAT,
+ MI_FBI,
+ MI_ARMY,
+ MI_MEDIC,
+ MI_FIREMAN,
+ MI_MALE01,
+ MI_TAXI_D,
+ MI_PIMP,
+ MI_GANG01,
+ MI_GANG02,
+ MI_GANG03,
+ MI_GANG04,
+ MI_GANG05,
+ MI_GANG06,
+ MI_GANG07,
+ MI_GANG08,
+ MI_GANG09,
+ MI_GANG10,
+ MI_GANG11,
+ MI_GANG12,
+ MI_GANG13,
+ MI_GANG14,
+ MI_CRIMINAL01,
+ MI_CRIMINAL02,
+ MI_SPECIAL01,
+ MI_SPECIAL02,
+ MI_SPECIAL03,
+ MI_SPECIAL04,
+ MI_MALE02,
+ MI_MALE03,
+ MI_FATMALE01,
+ MI_FATMALE02,
+ MI_FEMALE01,
+ MI_FEMALE02,
+ MI_FEMALE03,
+ MI_FATFEMALE01,
+ MI_FATFEMALE02,
+
+ MI_RHINO = 122,
+ MI_COACH = 127,
+};
+
+void InitModelIndices(void);
+void MatchModelString(const char *name, int16 id);
+
+inline bool
+IsGlass(int16 id)
+{
+ return id == MI_GLASS1 ||
+ id == MI_GLASS2 ||
+ id == MI_GLASS3 ||
+ id == MI_GLASS4 ||
+ id == MI_GLASS5 ||
+ id == MI_GLASS6 ||
+ id == MI_GLASS7 ||
+ id == MI_GLASS8;
+}
diff --git a/src/modelinfo/ModelInfo.cpp b/src/modelinfo/ModelInfo.cpp
new file mode 100644
index 00000000..89fcdee5
--- /dev/null
+++ b/src/modelinfo/ModelInfo.cpp
@@ -0,0 +1,124 @@
+#include "common.h"
+#include "patcher.h"
+#include "ModelInfo.h"
+
+CBaseModelInfo **CModelInfo::ms_modelInfoPtrs = (CBaseModelInfo**)0x83D408;
+
+//CStore<CSimpleModelInfo, SIMPLEMODELSIZE> &CModelInfo::ms_simpleModelStore = *(CStore<CSimpleModelInfo, SIMPLEMODELSIZE>*)0x885BB4;
+//CStore<CTimeModelInfo, TIMEMODELSIZE> &CModelInfo::ms_timeModelStore = *(CStore<CTimeModelInfo, TIMEMODELSIZE>*)0x94076C;
+//CStore<C2dEffect, TWODFXSIZE> &CModelInfo::ms_2dEffectStore = *(CStore<C2dEffect, TWODFXSIZE>*)0x9434F8;
+CStore<CSimpleModelInfo, SIMPLEMODELSIZE> CModelInfo::ms_simpleModelStore;
+CStore<CTimeModelInfo, TIMEMODELSIZE> CModelInfo::ms_timeModelStore;
+CStore<CClumpModelInfo, CLUMPMODELSIZE> CModelInfo::ms_clumpModelStore;
+CStore<CPedModelInfo, PEDMODELSIZE> CModelInfo::ms_pedModelStore;
+CStore<CVehicleModelInfo, VEHICLEMODELSIZE> CModelInfo::ms_vehicleModelStore;
+CStore<C2dEffect, TWODFXSIZE> CModelInfo::ms_2dEffectStore;
+
+void
+CModelInfo::Initialise(void)
+{
+ int i;
+ for(i = 0; i < MODELINFOSIZE; i++)
+ ms_modelInfoPtrs[i] = nil;
+ ms_2dEffectStore.clear();
+ ms_simpleModelStore.clear();
+ ms_timeModelStore.clear();
+ ms_clumpModelStore.clear();
+ ms_pedModelStore.clear();
+ ms_vehicleModelStore.clear();
+}
+
+void
+CModelInfo::Shutdown(void)
+{
+ int i;
+ for(i = 0; i < ms_simpleModelStore.allocPtr; i++)
+ ms_simpleModelStore.store[i].Shutdown();
+ for(i = 0; i < ms_timeModelStore.allocPtr; i++)
+ ms_timeModelStore.store[i].Shutdown();
+ for(i = 0; i < ms_clumpModelStore.allocPtr; i++)
+ ms_clumpModelStore.store[i].Shutdown();
+ for(i = 0; i < ms_pedModelStore.allocPtr; i++)
+ ms_pedModelStore.store[i].Shutdown();
+ for(i = 0; i < ms_vehicleModelStore.allocPtr; i++)
+ ms_vehicleModelStore.store[i].Shutdown();
+}
+
+CSimpleModelInfo*
+CModelInfo::AddSimpleModel(int id)
+{
+ CSimpleModelInfo *modelinfo;
+ modelinfo = CModelInfo::ms_simpleModelStore.alloc();
+ CModelInfo::ms_modelInfoPtrs[id] = modelinfo;
+ modelinfo->Init();
+ return modelinfo;
+}
+
+CTimeModelInfo*
+CModelInfo::AddTimeModel(int id)
+{
+ CTimeModelInfo *modelinfo;
+ modelinfo = CModelInfo::ms_timeModelStore.alloc();
+ CModelInfo::ms_modelInfoPtrs[id] = modelinfo;
+ modelinfo->Init();
+ return modelinfo;
+}
+
+CClumpModelInfo*
+CModelInfo::AddClumpModel(int id)
+{
+ CClumpModelInfo *modelinfo;
+ modelinfo = CModelInfo::ms_clumpModelStore.alloc();
+ CModelInfo::ms_modelInfoPtrs[id] = modelinfo;
+ modelinfo->m_clump = nil;
+ return modelinfo;
+}
+
+CPedModelInfo*
+CModelInfo::AddPedModel(int id)
+{
+ CPedModelInfo *modelinfo;
+ modelinfo = CModelInfo::ms_pedModelStore.alloc();
+ CModelInfo::ms_modelInfoPtrs[id] = modelinfo;
+ modelinfo->m_clump = nil;
+ return modelinfo;
+}
+
+CVehicleModelInfo*
+CModelInfo::AddVehicleModel(int id)
+{
+ CVehicleModelInfo *modelinfo;
+ modelinfo = CModelInfo::ms_vehicleModelStore.alloc();
+ CModelInfo::ms_modelInfoPtrs[id] = modelinfo;
+ modelinfo->m_clump = nil;
+ modelinfo->m_vehicleType = -1;
+ modelinfo->m_wheelId = -1;
+ modelinfo->m_materials1[0] = nil;
+ modelinfo->m_materials2[0] = nil;
+ modelinfo->m_bikeSteerAngle = 999.99f;
+ return modelinfo;
+}
+
+CBaseModelInfo*
+CModelInfo::GetModelInfo(const char *name, int *id)
+{
+ CBaseModelInfo *modelinfo;
+ for(int i = 0; i < MODELINFOSIZE; i++){
+ modelinfo = CModelInfo::ms_modelInfoPtrs[i];
+ if(modelinfo && _strcmpi(modelinfo->GetName(), name) == 0){
+ if(id)
+ *id = i;
+ return modelinfo;
+ }
+ }
+ return nil;
+}
+
+STARTPATCHES
+// InjectHook(0x50B920, CModelInfo::AddSimpleModel, PATCH_JUMP);
+// InjectHook(0x50B9C0, CModelInfo::AddTimeModel, PATCH_JUMP);
+// InjectHook(0x50BA10, CModelInfo::AddClumpModel, PATCH_JUMP);
+// InjectHook(0x50BAD0, CModelInfo::AddPedModel, PATCH_JUMP);
+// InjectHook(0x50BA60, CModelInfo::AddPedModel, PATCH_JUMP);
+ InjectHook(0x50B860, (CBaseModelInfo *(*)(const char*, int*))CModelInfo::GetModelInfo, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/ModelInfo.h b/src/modelinfo/ModelInfo.h
new file mode 100644
index 00000000..a0d30dea
--- /dev/null
+++ b/src/modelinfo/ModelInfo.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "2dEffect.h"
+#include "BaseModelInfo.h"
+#include "SimpleModelInfo.h"
+#include "TimeModelInfo.h"
+#include "ClumpModelInfo.h"
+#include "PedModelInfo.h"
+#include "VehicleModelInfo.h"
+
+class CModelInfo
+{
+ static CBaseModelInfo **ms_modelInfoPtrs; //[MODELINFOSIZE];
+ static CStore<CSimpleModelInfo, SIMPLEMODELSIZE> ms_simpleModelStore;
+ static CStore<CTimeModelInfo, TIMEMODELSIZE> ms_timeModelStore;
+ static CStore<CClumpModelInfo, CLUMPMODELSIZE> ms_clumpModelStore;
+ static CStore<CPedModelInfo, PEDMODELSIZE> ms_pedModelStore;
+ static CStore<CVehicleModelInfo, VEHICLEMODELSIZE> ms_vehicleModelStore;
+ static CStore<C2dEffect, TWODFXSIZE> ms_2dEffectStore;
+
+public:
+ static void Initialise(void);
+ static void Shutdown(void);
+
+ static CSimpleModelInfo *AddSimpleModel(int id);
+ static CTimeModelInfo *AddTimeModel(int id);
+ static CClumpModelInfo *AddClumpModel(int id);
+ static CPedModelInfo *AddPedModel(int id);
+ static CVehicleModelInfo *AddVehicleModel(int id);
+
+ static CBaseModelInfo *GetModelInfo(const char *name, int *id);
+ static CBaseModelInfo *GetModelInfo(int id){
+ return ms_modelInfoPtrs[id];
+ }
+};
diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp
new file mode 100644
index 00000000..e095902e
--- /dev/null
+++ b/src/modelinfo/PedModelInfo.cpp
@@ -0,0 +1,197 @@
+#include "common.h"
+#include "patcher.h"
+#include "NodeName.h"
+#include "VisibilityPlugins.h"
+#include "ModelInfo.h"
+
+void
+CPedModelInfo::DeleteRwObject(void)
+{
+ CClumpModelInfo::DeleteRwObject();
+ if(m_hitColModel)
+ delete m_hitColModel;
+ m_hitColModel = nil;
+}
+
+RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[12] = {
+ { "Smid", PED_TORSO, 0, }, // that is strange...
+ { "Shead", PED_HEAD, 0, },
+ { "Supperarml", PED_UPPERARML, 0, },
+ { "Supperarmr", PED_UPPERARMR, 0, },
+ { "SLhand", PED_HANDL, 0, },
+ { "SRhand", PED_HANDR, 0, },
+ { "Supperlegl", PED_UPPERLEGL, 0, },
+ { "Supperlegr", PED_UPPERLEGR, 0, },
+ { "Sfootl", PED_FOOTL, 0, },
+ { "Sfootr", PED_FOOTR, 0, },
+ { "Slowerlegr", PED_LOWERLEGR, 0, },
+ { NULL, 0, 0, },
+};
+
+void
+CPedModelInfo::SetClump(RpClump *clump)
+{
+ CClumpModelInfo::SetClump(clump);
+ SetFrameIds(m_pPedIds);
+ if(m_hitColModel == nil)
+ CreateHitColModel();
+ if(strncmp(GetName(), "player", 7) == 0)
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPlayerCB);
+}
+
+RpAtomic*
+CountAtomicsCB(RpAtomic *atomic, void *data)
+{
+ (*(int32*)data)++;
+ return atomic;
+}
+
+RpAtomic*
+GetAtomicListCB(RpAtomic *atomic, void *data)
+{
+ **(RpAtomic***)data = atomic;
+ (*(RpAtomic***)data)++;
+ return atomic;
+}
+
+RwFrame*
+FindPedFrameFromNameCB(RwFrame *frame, void *data)
+{
+ RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data;
+
+ if(_strcmpi(GetFrameNodeName(frame)+1, assoc->name+1) != 0){
+ RwFrameForAllChildren(frame, FindPedFrameFromNameCB, assoc);
+ return assoc->frame ? nil : frame;
+ }else{
+ assoc->frame = frame;
+ return nil;
+ }
+}
+
+void
+CPedModelInfo::SetLowDetailClump(RpClump *lodclump)
+{
+ RpAtomic *atomics[16];
+ RpAtomic **pAtm;
+ int32 numAtm, numLodAtm;
+ int i;
+ RwObjectNameAssociation assoc;
+
+ numAtm = 0;
+ numLodAtm = 0;
+ RpClumpForAllAtomics(m_clump, CountAtomicsCB, &numAtm); // actually unused
+ RpClumpForAllAtomics(lodclump, CountAtomicsCB, &numLodAtm);
+
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, CVisibilityPlugins::RenderPedHiDetailCB);
+ RpClumpForAllAtomics(lodclump, SetAtomicRendererCB, CVisibilityPlugins::RenderPedLowDetailCB);
+
+ pAtm = atomics;
+ RpClumpForAllAtomics(lodclump, GetAtomicListCB, &pAtm);
+
+ for(i = 0; i < numLodAtm; i++){
+ assoc.name = GetFrameNodeName(RpAtomicGetFrame(atomics[i]));
+ assoc.frame = nil;
+ RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindPedFrameFromNameCB, &assoc);
+ if(assoc.frame){
+ RpAtomicSetFrame(atomics[i], assoc.frame);
+ RpClumpRemoveAtomic(lodclump, atomics[i]);
+ RpClumpAddAtomic(m_clump, atomics[i]);
+ }
+ }
+}
+
+struct ColNodeInfo
+{
+ char *name;
+ int pedNode;
+ int pieceType;
+ float x, z;
+ float radius;
+};
+
+// TODO: find out piece types
+#define NUMPEDINFONODES 8
+ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = {
+ { NULL, PED_HEAD, 6, 0.0f, 0.05f, 0.2f },
+ { "Storso", 0, 0, 0.0f, 0.15f, 0.2f },
+ { "Storso", 0, 0, 0.0f, -0.05f, 0.3f },
+ { NULL, PED_TORSO, 1, 0.0f, -0.07f, 0.3f },
+ { NULL, PED_UPPERARML, 2, 0.07f, -0.1f, 0.2f },
+ { NULL, PED_UPPERARMR, 3, -0.07f, -0.1f, 0.2f },
+ { "Slowerlegl", 0, 4, 0.0f, 0.07f, 0.25f },
+ { NULL, PED_LOWERLEGR, 5, 0.0f, 0.07f, 0.25f },
+};
+
+RwObject*
+FindHeadRadiusCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ *(float*)data = RpAtomicGetBoundingSphere(atomic)->radius;
+ return nil;
+}
+
+void
+CPedModelInfo::CreateHitColModel(void)
+{
+ RwObjectNameAssociation nameAssoc;
+ RwObjectIdAssociation idAssoc;
+ CVector center;
+ RwFrame *nodeFrame;
+ CColModel *colmodel = new CColModel;
+ CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere));
+ RwFrame *root = RpClumpGetFrame(m_clump);
+ RwMatrix *mat = RwMatrixCreate();
+ for(int i = 0; i < NUMPEDINFONODES; i++){
+ nodeFrame = nil;
+ if(m_pColNodeInfos[i].name){
+ nameAssoc.name = m_pColNodeInfos[i].name;
+ nameAssoc.frame = nil;
+ RwFrameForAllChildren(root, FindFrameFromNameCB, &nameAssoc);
+ nodeFrame = nameAssoc.frame;
+ }else{
+ idAssoc.id = m_pColNodeInfos[i].pedNode;
+ idAssoc.frame = nil;
+ RwFrameForAllChildren(root, FindFrameFromIdCB, &idAssoc);
+ nodeFrame = idAssoc.frame;
+ }
+ if(nodeFrame){
+ float radius = m_pColNodeInfos[i].radius;
+ if(m_pColNodeInfos[i].pieceType == 6)
+ RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius);
+ RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE);
+ const char *name = GetFrameNodeName(nodeFrame);
+ for(nodeFrame = RwFrameGetParent(nodeFrame);
+ nodeFrame;
+ nodeFrame = RwFrameGetParent(nodeFrame)){
+ name = GetFrameNodeName(nodeFrame);
+ RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEPOSTCONCAT);
+ if(RwFrameGetParent(nodeFrame) == root)
+ break;
+ }
+ center.x = mat->pos.x + m_pColNodeInfos[i].x;
+ center.y = mat->pos.y + 0.0f;
+ center.z = mat->pos.z + m_pColNodeInfos[i].z;
+ spheres[i].Set(radius, center, 17, m_pColNodeInfos[i].pieceType);
+ }
+ }
+ RwMatrixDestroy(mat);
+ colmodel->spheres = spheres;
+ colmodel->numSpheres = NUMPEDINFONODES;
+ center.x = center.y = center.z = 0.0f;
+ colmodel->boundingSphere.Set(2.0f, center, 0, 0);
+ CVector min, max;
+ min.x = min.y = -0.5f;
+ min.z = -1.2f;
+ max.x = max.y = 0.5f;
+ max.z = 1.2f;
+ colmodel->boundingBox.Set(min, max, 0, 0);
+ colmodel->level = 0;
+ m_hitColModel = colmodel;
+}
+
+STARTPATCHES
+ InjectHook(0x510210, &CPedModelInfo::SetClump_, PATCH_JUMP);
+ InjectHook(0x510280, &CPedModelInfo::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x510390, &CPedModelInfo::SetLowDetailClump, PATCH_JUMP);
+ InjectHook(0x5104D0, &CPedModelInfo::CreateHitColModel, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/PedModelInfo.h b/src/modelinfo/PedModelInfo.h
new file mode 100644
index 00000000..e917b6b2
--- /dev/null
+++ b/src/modelinfo/PedModelInfo.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "ClumpModelInfo.h"
+
+enum PedNode {
+ PED_WAIST,
+ PED_TORSO, // Smid on PS2/PC, Storso on mobile/xbox
+ PED_HEAD,
+ PED_UPPERARML,
+ PED_UPPERARMR,
+ PED_HANDL,
+ PED_HANDR,
+ PED_UPPERLEGL,
+ PED_UPPERLEGR,
+ PED_FOOTL,
+ PED_FOOTR,
+ PED_LOWERLEGR,
+ // This is not valid apparently
+ PED_LOWERLEGL,
+};
+
+class CPedModelInfo : public CClumpModelInfo
+{
+public:
+ void *m_animGroup; // TODO
+ int32 m_pedType;
+ int32 m_pedStatType;
+ uint32 m_carsCanDrive;
+ CColModel *m_hitColModel;
+ RpAtomic *m_head;
+ RpAtomic *m_lhand;
+ RpAtomic *m_rhand;
+
+ static RwObjectNameIdAssocation m_pPedIds[12];
+
+ CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { }
+ void DeleteRwObject(void);
+ void SetClump(RpClump *);
+
+ void SetLowDetailClump(RpClump*);
+ void CreateHitColModel(void);
+
+
+ void DeleteRwObject_(void) { this->CPedModelInfo::DeleteRwObject(); }
+ void SetClump_(RpClump *clump) { this->CPedModelInfo::SetClump(clump); }
+};
+static_assert(sizeof(CPedModelInfo) == 0x54, "CPedModelInfo: error");
diff --git a/src/modelinfo/SimpleModelInfo.cpp b/src/modelinfo/SimpleModelInfo.cpp
new file mode 100644
index 00000000..a5853d6f
--- /dev/null
+++ b/src/modelinfo/SimpleModelInfo.cpp
@@ -0,0 +1,174 @@
+#include "common.h"
+#include "patcher.h"
+#include "Camera.h"
+#include "ModelInfo.h"
+
+#define LOD_DISTANCE (300.0f)
+
+void
+CSimpleModelInfo::DeleteRwObject(void)
+{
+ int i;
+ RwFrame *f;
+ for(i = 0; i < m_numAtomics; i++)
+ if(m_atomics[i]){
+ f = RpAtomicGetFrame(m_atomics[i]);
+ RpAtomicDestroy(m_atomics[i]);
+ RwFrameDestroy(f);
+ m_atomics[i] = nil;
+ RemoveTexDictionaryRef();
+ }
+}
+
+RwObject*
+CSimpleModelInfo::CreateInstance(void)
+{
+ RpAtomic *atomic;
+ if(m_atomics[0] == nil)
+ return nil;
+ atomic = RpAtomicClone(m_atomics[0]);
+ RpAtomicSetFrame(atomic, RwFrameCreate());
+ return (RwObject*)atomic;
+}
+
+RwObject*
+CSimpleModelInfo::CreateInstance(RwMatrix *matrix)
+{
+ RpAtomic *atomic;
+ RwFrame *frame;
+
+ if(m_atomics[0] == nil)
+ return nil;
+ atomic = RpAtomicClone(m_atomics[0]);
+ frame = RwFrameCreate();
+ *RwFrameGetMatrix(frame) = *matrix;
+ RpAtomicSetFrame(atomic, frame);
+ return (RwObject*)atomic;
+}
+
+void
+CSimpleModelInfo::Init(void)
+{
+ m_atomics[0] = nil;
+ m_atomics[1] = nil;
+ m_atomics[2] = nil;
+ m_numAtomics = 0;
+ m_furthest = 0;
+ m_normalCull = 0;
+ m_isDamaged = 0;
+ m_isBigBuilding = 0;
+ m_noFade = 0;
+ m_drawLast = 0;
+ m_additive = 0;
+ m_isSubway = 0;
+ m_ignoreLight = 0;
+ m_noZwrite = 0;
+}
+
+void
+CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic)
+{
+ AddTexDictionaryRef();
+ m_atomics[n] = atomic;
+ if(m_ignoreLight){
+ RpGeometry *geo = RpAtomicGetGeometry(atomic);
+ RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT);
+ }
+}
+
+void
+CSimpleModelInfo::SetLodDistances(float *dist)
+{
+ m_lodDistances[0] = dist[0];
+ m_lodDistances[1] = dist[1];
+ m_lodDistances[2] = dist[2];
+}
+
+void
+CSimpleModelInfo::IncreaseAlpha(void)
+{
+ if(m_alpha >= 0xEF)
+ m_alpha = 0xFF;
+ else
+ m_alpha += 0x10;
+}
+
+float
+CSimpleModelInfo::GetNearDistance(void)
+{
+ return m_lodDistances[2] * TheCamera.LODDistMultiplier;
+}
+
+float
+CSimpleModelInfo::GetLargestLodDistance(void)
+{
+ float d;
+ // TODO: what exactly is going on here?
+ if(m_furthest != 0 && !m_isDamaged)
+ d = m_lodDistances[m_furthest-1];
+ else
+ d = m_lodDistances[m_numAtomics-1];
+ return d * TheCamera.LODDistMultiplier;
+}
+
+RpAtomic*
+CSimpleModelInfo::GetAtomicFromDistance(float dist)
+{
+ int i;
+ i = 0;
+ // TODO: what exactly is going on here?
+ if(m_isDamaged)
+ i = m_furthest;
+ for(; i < m_numAtomics; i++)
+ if(dist < m_lodDistances[i] *TheCamera.LODDistMultiplier)
+ return m_atomics[i];
+ return nil;
+}
+
+void
+CSimpleModelInfo::FindRelatedModel(void)
+{
+ int i;
+ CBaseModelInfo *mi;
+ for(i = 0; i < MODELINFOSIZE; i++){
+ mi = CModelInfo::GetModelInfo(i);
+ if(mi && mi != this &&
+ strcmp(GetName()+3, mi->GetName()+3) == 0){
+ assert(mi->IsSimple());
+ this->SetRelatedModel((CSimpleModelInfo*)mi);
+ return;
+ }
+ }
+}
+
+void
+CSimpleModelInfo::SetupBigBuilding(void)
+{
+ CSimpleModelInfo *related;
+ if(m_lodDistances[0] > LOD_DISTANCE && m_atomics[2] == nil){
+ m_isBigBuilding = 1;
+ FindRelatedModel();
+ related = GetRelatedModel();
+ if(related)
+ m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier;
+ else
+ m_lodDistances[2] = 100.0f;
+ }
+}
+
+
+STARTPATCHES
+ InjectHook(0x5179B0, &CSimpleModelInfo::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x517B60, &CSimpleModelInfo::CreateInstance_1, PATCH_JUMP);
+ InjectHook(0x517AC0, &CSimpleModelInfo::CreateInstance_2, PATCH_JUMP);
+ InjectHook(0x4A9BA0, &CSimpleModelInfo::GetRwObject_, PATCH_JUMP);
+ InjectHook(0x517990, &CSimpleModelInfo::Init, PATCH_JUMP);
+ InjectHook(0x517C60, &CSimpleModelInfo::IncreaseAlpha, PATCH_JUMP);
+ InjectHook(0x517950, &CSimpleModelInfo::SetAtomic, PATCH_JUMP);
+ InjectHook(0x517AA0, &CSimpleModelInfo::SetLodDistances, PATCH_JUMP);
+ InjectHook(0x517A90, &CSimpleModelInfo::GetNearDistance, PATCH_JUMP);
+ InjectHook(0x517A60, &CSimpleModelInfo::GetLargestLodDistance, PATCH_JUMP);
+ InjectHook(0x517A00, &CSimpleModelInfo::GetAtomicFromDistance, PATCH_JUMP);
+ InjectHook(0x517C00, &CSimpleModelInfo::FindRelatedModel, PATCH_JUMP);
+ InjectHook(0x517B90, &CSimpleModelInfo::SetupBigBuilding, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/SimpleModelInfo.h b/src/modelinfo/SimpleModelInfo.h
new file mode 100644
index 00000000..186a517c
--- /dev/null
+++ b/src/modelinfo/SimpleModelInfo.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "BaseModelInfo.h"
+
+class CSimpleModelInfo : public CBaseModelInfo
+{
+public:
+ // atomics[2] is often a pointer to the non-LOD modelinfo
+ RpAtomic *m_atomics[3];
+ // m_lodDistances[2] holds the near distance for LODs
+ float m_lodDistances[3];
+ uint8 m_numAtomics;
+ uint8 m_alpha;
+ uint16 m_furthest : 2; // 0: numAtomics-1 is furthest visible
+ // 1: atomic 0 is furthest
+ // 2: atomic 1 is furthest
+ uint16 m_normalCull : 1;
+ uint16 m_isDamaged : 1;
+ uint16 m_isBigBuilding : 1;
+ uint16 m_noFade : 1;
+ uint16 m_drawLast : 1;
+ uint16 m_additive : 1;
+ uint16 m_isSubway : 1;
+ uint16 m_ignoreLight : 1;
+ uint16 m_noZwrite : 1;
+
+ CSimpleModelInfo(void) : CBaseModelInfo(MITYPE_SIMPLE) {}
+ CSimpleModelInfo(ModeInfoType id) : CBaseModelInfo(id) {}
+ ~CSimpleModelInfo() {}
+ void DeleteRwObject(void);
+ RwObject *CreateInstance(void);
+ RwObject *CreateInstance(RwMatrix *);
+ RwObject *GetRwObject(void) { return (RwObject*)m_atomics[0]; }
+
+ void Init(void);
+ void IncreaseAlpha(void);
+ void SetAtomic(int n, RpAtomic *atomic);
+ void SetLodDistances(float *dist);
+ float GetLodDistance(int i) { return m_lodDistances[i]; }
+ float GetNearDistance(void);
+ float GetLargestLodDistance(void);
+ RpAtomic *GetAtomicFromDistance(float dist);
+ void FindRelatedModel(void);
+ void SetupBigBuilding(void);
+
+ void SetNumAtomics(int n) { m_numAtomics = n; }
+ CSimpleModelInfo *GetRelatedModel(void){
+ return (CSimpleModelInfo*)m_atomics[2]; }
+ void SetRelatedModel(CSimpleModelInfo *m){
+ m_atomics[2] = (RpAtomic*)m; }
+
+ void DeleteRwObject_(void) { this->CSimpleModelInfo::DeleteRwObject(); }
+ RwObject *CreateInstance_1(void) { return this->CSimpleModelInfo::CreateInstance(); }
+ RwObject *CreateInstance_2(RwMatrix *m) { return this->CSimpleModelInfo::CreateInstance(m); }
+ RwObject *GetRwObject_(void) { return this->CSimpleModelInfo::GetRwObject(); }
+};
+static_assert(sizeof(CSimpleModelInfo) == 0x4C, "CSimpleModelInfo: error");
diff --git a/src/modelinfo/TimeModelInfo.cpp b/src/modelinfo/TimeModelInfo.cpp
new file mode 100644
index 00000000..3ab3e13a
--- /dev/null
+++ b/src/modelinfo/TimeModelInfo.cpp
@@ -0,0 +1,36 @@
+#include "common.h"
+#include "patcher.h"
+#include "Camera.h"
+#include "ModelInfo.h"
+
+CTimeModelInfo*
+CTimeModelInfo::FindOtherTimeModel(void)
+{
+ char name[40];
+ char *p;
+ int i;
+
+ strcpy(name, GetName());
+ // change _nt to _dy
+ if(p = strstr(name, "_nt"))
+ strncpy(p, "_dy", 4);
+ // change _dy to _nt
+ else if(p = strstr(name, "_dy"))
+ strncpy(p, "_nt", 4);
+ else
+ return nil;
+
+ for(i = 0; i < MODELINFOSIZE; i++){
+ CBaseModelInfo *mi = CModelInfo::GetModelInfo(i);
+ if(mi && mi->m_type == MITYPE_TIME &&
+ strncmp(name, mi->GetName(), 24) == 0){
+ m_otherTimeModelID = i;
+ return (CTimeModelInfo*)mi;
+ }
+ }
+ return nil;
+}
+
+STARTPATCHES
+ InjectHook(0x517C80, &CTimeModelInfo::FindOtherTimeModel, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/TimeModelInfo.h b/src/modelinfo/TimeModelInfo.h
new file mode 100644
index 00000000..08e46bd3
--- /dev/null
+++ b/src/modelinfo/TimeModelInfo.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "SimpleModelInfo.h"
+
+class CTimeModelInfo : public CSimpleModelInfo
+{
+ int32 m_timeOn;
+ int32 m_timeOff;
+ int32 m_otherTimeModelID;
+public:
+ CTimeModelInfo(void) : CSimpleModelInfo(MITYPE_TIME) { m_otherTimeModelID = -1; }
+
+ int32 GetTimeOn(void) { return m_timeOn; }
+ int32 GetTimeOff(void) { return m_timeOff; }
+ int32 GetOtherTimeModel(void) { return m_otherTimeModelID; }
+ CTimeModelInfo *FindOtherTimeModel(void);
+};
+static_assert(sizeof(CTimeModelInfo) == 0x58, "CTimeModelInfo: error");
diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/modelinfo/VehicleModelInfo.cpp
new file mode 100644
index 00000000..575d0360
--- /dev/null
+++ b/src/modelinfo/VehicleModelInfo.cpp
@@ -0,0 +1,917 @@
+#include "common.h"
+#include <rpmatfx.h>
+#include "patcher.h"
+#include "RwHelper.h"
+#include "General.h"
+#include "NodeName.h"
+#include "TxdStore.h"
+#include "Weather.h"
+#include "VisibilityPlugins.h"
+#include "ModelInfo.h"
+
+int8 *CVehicleModelInfo::ms_compsToUse = (int8*)0x5FF2EC; // -2, -2
+int8 *CVehicleModelInfo::ms_compsUsed = (int8*)0x95CCB2;
+RwTexture **CVehicleModelInfo::ms_pEnvironmentMaps = (RwTexture **)0x8F1A30;
+RwRGBA *CVehicleModelInfo::ms_vehicleColourTable = (RwRGBA*)0x86BA88;
+RwTexture **CVehicleModelInfo::ms_colourTextureTable = (RwTexture**)0x711C40;
+
+RwTexture *&gpWhiteTexture = *(RwTexture**)0x64C4F8;
+RwFrame *&pMatFxIdentityFrame = *(RwFrame**)0x64C510;
+
+// TODO This depends on handling
+WRAPPER void CVehicleModelInfo::SetVehicleComponentFlags(RwFrame *frame, uint32 flags) { EAXJMP(0x5203C0); }
+
+enum {
+ CAR_WHEEL_RF = 1,
+ CAR_WHEEL_RM = 2,
+ CAR_WHEEL_RB = 3,
+ CAR_WHEEL_LF = 4,
+ CAR_WHEEL_LM = 5,
+ CAR_WHEEL_LB = 6,
+ CAR_BUMP_FRONT = 7,
+ CAR_BUMP_REAR = 8,
+ CAR_WING_RF = 9,
+ CAR_WING_RR = 10,
+ CAR_DOOR_RF = 11,
+ CAR_DOOR_RR = 12,
+ CAR_WING_LF = 13,
+ CAR_WING_LR = 14,
+ CAR_DOOR_LF = 15,
+ CAR_DOOR_LR = 16,
+ CAR_BONNET = 17,
+ CAR_BOOT = 18,
+ CAR_WINDSCREEN = 19,
+
+ CAR_POS_HEADLIGHTS = 0,
+ CAR_POS_TAILLIGHTS = 1,
+ CAR_POS_FRONTSEAT = 2,
+ CAR_POS_BACKSEAT = 3,
+ CAR_POS_EXHAUST = 9,
+};
+
+enum {
+ VEHICLE_FLAG_COLLAPSE = 0x2,
+ VEHICLE_FLAG_ADD_WHEEL = 0x4,
+ VEHICLE_FLAG_POS = 0x8,
+ VEHICLE_FLAG_DOOR = 0x10,
+ VEHICLE_FLAG_LEFT = 0x20,
+ VEHICLE_FLAG_RIGHT = 0x40,
+ VEHICLE_FLAG_FRONT = 0x80,
+ VEHICLE_FLAG_REAR = 0x100,
+ VEHICLE_FLAG_COMP = 0x200,
+ VEHICLE_FLAG_DRAWLAST = 0x400,
+ VEHICLE_FLAG_WINDSCREEN = 0x800,
+ VEHICLE_FLAG_ANGLECULL = 0x1000,
+ VEHICLE_FLAG_REARDOOR = 0x2000,
+ VEHICLE_FLAG_FRONTDOOR = 0x4000,
+};
+
+RwObjectNameIdAssocation carIds[] = {
+ { "wheel_rf_dummy", CAR_WHEEL_RF, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL },
+ { "wheel_rm_dummy", CAR_WHEEL_RM, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL },
+ { "wheel_rb_dummy", CAR_WHEEL_RB, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL },
+ { "wheel_lf_dummy", CAR_WHEEL_LF, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL },
+ { "wheel_lm_dummy", CAR_WHEEL_LM, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL },
+ { "wheel_lb_dummy", CAR_WHEEL_LB, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL },
+ { "bump_front_dummy", CAR_BUMP_FRONT, VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE },
+ { "bonnet_dummy", CAR_BONNET, VEHICLE_FLAG_COLLAPSE },
+ { "wing_rf_dummy", CAR_WING_RF, VEHICLE_FLAG_COLLAPSE },
+ { "wing_rr_dummy", CAR_WING_RR, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_COLLAPSE },
+ { "door_rf_dummy", CAR_DOOR_RF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE },
+ { "door_rr_dummy", CAR_DOOR_RR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE },
+ { "wing_lf_dummy", CAR_WING_LF, VEHICLE_FLAG_COLLAPSE },
+ { "wing_lr_dummy", CAR_WING_LR, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE },
+ { "door_lf_dummy", CAR_DOOR_LF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE },
+ { "door_lr_dummy", CAR_DOOR_LR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE },
+ { "boot_dummy", CAR_BOOT, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE },
+ { "bump_rear_dummy", CAR_BUMP_REAR, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE },
+ { "windscreen_dummy", CAR_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE },
+
+ { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "ped_backseat", CAR_POS_BACKSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation boatIds[] = {
+ { "boat_moving_hi", 1, VEHICLE_FLAG_COLLAPSE },
+ { "boat_rudder_hi", 3, VEHICLE_FLAG_COLLAPSE },
+ { "windscreen", 2, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_COLLAPSE },
+ { "ped_frontseat", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation trainIds[] = {
+ { "door_lhs_dummy", 1, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE },
+ { "door_rhs_dummy", 2, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE },
+ { "light_front", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "light_rear", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "ped_left_entry", 2, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "ped_mid_entry", 3, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "ped_right_entry", 4, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation heliIds[] = {
+ { "chassis_dummy", 1, VEHICLE_FLAG_COLLAPSE },
+ { "toprotor", 2, 0 },
+ { "backrotor", 3, 0 },
+ { "tail", 4, 0 },
+ { "topknot", 5, 0 },
+ { "skid_left", 6, 0 },
+ { "skid_right", 7, 0 },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation planeIds[] = {
+ { "wheel_front_dummy", 2, 0 },
+ { "wheel_rear_dummy", 3, 0 },
+ { "light_tailplane", 2, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "light_left", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "light_right", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation bikeIds[] = {
+ { "chassis_dummy", 1, 0 },
+ { "forks_front", 2, 0 },
+ { "forks_rear", 3, 0 },
+ { "wheel_front", 4, 0 },
+ { "wheel_rear", 5, 0 },
+ { "mudguard", 6, 0 },
+ { "ped_frontseat", 2, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "headlights", 0, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "taillights", 1, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "exhaust", 9, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID },
+ { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID },
+ { nil, 0, 0 }
+};
+
+RwObjectNameIdAssocation *CVehicleModelInfo::ms_vehicleDescs[] = {
+ carIds,
+ boatIds,
+ trainIds,
+ heliIds,
+ planeIds,
+ bikeIds
+};
+
+
+CVehicleModelInfo::CVehicleModelInfo(void)
+ : CClumpModelInfo(MITYPE_VEHICLE)
+{
+ int32 i;
+ for(i = 0; i < NUM_VEHICLE_POSITIONS; i++){
+ m_positions[i].x = 0.0f;
+ m_positions[i].y = 0.0f;
+ m_positions[i].z = 0.0f;
+ }
+ m_numColours = 0;
+}
+
+void
+CVehicleModelInfo::DeleteRwObject(void)
+{
+ int32 i;
+ RwFrame *f;
+
+ for(i = 0; i < m_numComps; i++){
+ f = RpAtomicGetFrame(m_comps[i]);
+ RpAtomicDestroy(m_comps[i]);
+ RwFrameDestroy(f);
+ }
+ m_numComps = 0;
+ CClumpModelInfo::DeleteRwObject();
+}
+
+RwObject*
+CVehicleModelInfo::CreateInstance(void)
+{
+ RpClump *clump;
+ RpAtomic *atomic;
+ RwFrame *clumpframe, *f;
+ int32 comp1, comp2;
+
+ clump = (RpClump*)CClumpModelInfo::CreateInstance();
+ if(m_numComps != 0){
+ clumpframe = RpClumpGetFrame(clump);
+
+ comp1 = ChooseComponent();
+ if(comp1 != -1){
+ atomic = RpAtomicClone(m_comps[comp1]);
+ f = RwFrameCreate();
+ RwFrameTransform(f,
+ RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp1])),
+ rwCOMBINEREPLACE);
+ RpAtomicSetFrame(atomic, f);
+ RpClumpAddAtomic(clump, atomic);
+ RwFrameAddChild(clumpframe, f);
+ }
+ ms_compsUsed[0] = comp1;
+
+ comp2 = ChooseSecondComponent();
+ if(comp2 != -1){
+ atomic = RpAtomicClone(m_comps[comp2]);
+ f = RwFrameCreate();
+ RwFrameTransform(f,
+ RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp2])),
+ rwCOMBINEREPLACE);
+ RpAtomicSetFrame(atomic, f);
+ RpClumpAddAtomic(clump, atomic);
+ RwFrameAddChild(clumpframe, f);
+ }
+ ms_compsUsed[1] = comp2;
+ }else{
+ ms_compsUsed[0] = -1;
+ ms_compsUsed[1] = -1;
+ }
+ return (RwObject*)clump;
+}
+
+void
+CVehicleModelInfo::SetClump(RpClump *clump)
+{
+ CClumpModelInfo::SetClump(clump);
+ SetAtomicRenderCallbacks();
+ SetFrameIds(ms_vehicleDescs[m_vehicleType]);
+ PreprocessHierarchy();
+ FindEditableMaterialList();
+ m_envMap = nil;
+ SetEnvironmentMap();
+}
+
+RwFrame*
+CVehicleModelInfo::CollapseFramesCB(RwFrame *frame, void *data)
+{
+ RwFrameForAllChildren(frame, CollapseFramesCB, data);
+ RwFrameForAllObjects(frame, MoveObjectsCB, data);
+ RwFrameDestroy(frame);
+ return frame;
+}
+
+RwObject*
+CVehicleModelInfo::MoveObjectsCB(RwObject *object, void *data)
+{
+ RpAtomicSetFrame((RpAtomic*)object, (RwFrame*)data);
+ return object;
+}
+
+RpAtomic*
+CVehicleModelInfo::HideDamagedAtomicCB(RpAtomic *atomic, void *data)
+{
+ if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_dam")){
+ RpAtomicSetFlags(atomic, 0);
+ CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_DAM);
+ }else if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_ok"))
+ CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_OK);
+ return atomic;
+}
+
+RpMaterial*
+CVehicleModelInfo::HasAlphaMaterialCB(RpMaterial *material, void *data)
+{
+ if(RpMaterialGetColor(material)->alpha != 0xFF){
+ *(bool*)data = true;
+ return nil;
+ }
+ return material;
+}
+
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data)
+{
+ RpClump *clump;
+ char *name;
+ bool alpha;
+
+ clump = (RpClump*)data;
+ name = GetFrameNodeName(RpAtomicGetFrame(atomic));
+ alpha = false;
+ RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha);
+ if(strstr(name, "_hi") || strncmp(name, "extra", 5) == 0){
+ if(alpha || strncmp(name, "windscreen", 10) == 0)
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB);
+ }else if(strstr(name, "_lo")){
+ RpClumpRemoveAtomic(clump, atomic);
+ RpAtomicDestroy(atomic);
+ return atomic; // BUG: not done by gta
+ }else if(strstr(name, "_vlo"))
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ HideDamagedAtomicCB(atomic, nil);
+ return atomic;
+}
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data)
+{
+ char *name;
+ bool alpha;
+
+ name = GetFrameNodeName(RpAtomicGetFrame(atomic));
+ alpha = false;
+ RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha);
+ if(strstr(name, "_hi") || strncmp(name, "extra", 5) == 0){
+ if(alpha)
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle);
+ }else if(strstr(name, "_lo")){
+ if(alpha)
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle);
+ }else if(strstr(name, "_vlo"))
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ HideDamagedAtomicCB(atomic, nil);
+ return atomic;
+}
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicRendererCB_Train(RpAtomic *atomic, void *data)
+{
+ char *name;
+ bool alpha;
+
+ name = GetFrameNodeName(RpAtomicGetFrame(atomic));
+ alpha = false;
+ RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha);
+ if(strstr(name, "_hi")){
+ if(alpha)
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailAlphaCB);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailCB);
+ }else if(strstr(name, "_vlo"))
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ HideDamagedAtomicCB(atomic, nil);
+ return atomic;
+}
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data)
+{
+ RpClump *clump;
+ char *name;
+
+ clump = (RpClump*)data;
+ name = GetFrameNodeName(RpAtomicGetFrame(atomic));
+ if(strcmp(name, "boat_hi") == 0 || strncmp(name, "extra", 5) == 0)
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat);
+ else if(strstr(name, "_hi"))
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB);
+ else if(strstr(name, "_lo")){
+ RpClumpRemoveAtomic(clump, atomic);
+ RpAtomicDestroy(atomic);
+ return atomic; // BUG: not done by gta
+ }else if(strstr(name, "_vlo"))
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle);
+ else
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ HideDamagedAtomicCB(atomic, nil);
+ return atomic;
+}
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data)
+{
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+ return atomic;
+}
+
+void
+CVehicleModelInfo::SetAtomicRenderCallbacks(void)
+{
+ switch(m_vehicleType){
+ case VEHICLE_TYPE_TRAIN:
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Train, nil);
+ break;
+ case VEHICLE_TYPE_HELI:
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Heli, nil);
+ break;
+ case VEHICLE_TYPE_PLANE:
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_BigVehicle, nil);
+ break;
+ case VEHICLE_TYPE_BOAT:
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Boat, m_clump);
+ break;
+ default:
+ RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, m_clump);
+ break;
+ }
+}
+
+RpAtomic*
+CVehicleModelInfo::SetAtomicFlagCB(RpAtomic *atomic, void *data)
+{
+ CVisibilityPlugins::SetAtomicFlag(atomic, (int)data);
+ return atomic;
+}
+
+RpAtomic*
+CVehicleModelInfo::ClearAtomicFlagCB(RpAtomic *atomic, void *data)
+{
+ CVisibilityPlugins::ClearAtomicFlag(atomic, (int)data);
+ return atomic;
+}
+
+RwObject*
+GetOkAndDamagedAtomicCB(RwObject *object, void *data)
+{
+ RpAtomic *atomic = (RpAtomic*)object;
+ if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_OK)
+ ((RpAtomic**)data)[0] = atomic;
+ else if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_DAM)
+ ((RpAtomic**)data)[1] = atomic;
+ return object;
+}
+
+void
+CVehicleModelInfo::PreprocessHierarchy(void)
+{
+ int32 i;
+ RwObjectNameIdAssocation *desc;
+ RwFrame *f;
+ RpAtomic *atomic;
+ RwV3d *rwvec;
+
+ desc = ms_vehicleDescs[m_vehicleType];
+ m_numDoors = 0;
+ m_numComps = 0;
+
+ for(i = 0; desc[i].name; i++){
+ RwObjectNameAssociation assoc;
+
+ if((desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) == 0)
+ continue;
+ assoc.frame = nil;
+ assoc.name = desc[i].name;
+ RwFrameForAllChildren(RpClumpGetFrame(m_clump),
+ FindFrameFromNameWithoutIdCB, &assoc);
+ if(assoc.frame == nil)
+ continue;
+
+ if(desc[i].flags & VEHICLE_FLAG_DOOR)
+ m_numDoors++;
+
+ if(desc[i].flags & VEHICLE_FLAG_POS){
+ f = assoc.frame;
+ rwvec = (RwV3d*)&m_positions[desc[i].hierId];
+ *rwvec = *RwMatrixGetPos(RwFrameGetMatrix(f));
+ for(f = RwFrameGetParent(f); f; f = RwFrameGetParent(f))
+ RwV3dTransformPoints(rwvec, rwvec, 1, RwFrameGetMatrix(f));
+ RwFrameDestroy(assoc.frame);
+ }else{
+ atomic = (RpAtomic*)GetFirstObject(assoc.frame);
+ RpClumpRemoveAtomic(m_clump, atomic);
+ RwFrameRemoveChild(assoc.frame);
+ SetVehicleComponentFlags(assoc.frame, desc[i].flags);
+ m_comps[m_numComps++] = atomic;
+ }
+ }
+
+ for(i = 0; desc[i].name; i++){
+ RwObjectIdAssociation assoc;
+
+ if(desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS))
+ continue;
+ assoc.frame = nil;
+ assoc.id = desc[i].hierId;
+ RwFrameForAllChildren(RpClumpGetFrame(m_clump),
+ FindFrameFromIdCB, &assoc);
+ if(assoc.frame == nil)
+ continue;
+
+ if(desc[i].flags & VEHICLE_FLAG_DOOR)
+ m_numDoors++;
+
+ if(desc[i].flags & VEHICLE_FLAG_COLLAPSE){
+ RpAtomic *okdam[2] = { nil, nil };
+ RwFrameForAllChildren(assoc.frame, CollapseFramesCB, assoc.frame);
+ RwFrameUpdateObjects(assoc.frame);
+ RwFrameForAllObjects(assoc.frame, GetOkAndDamagedAtomicCB, okdam);
+ if(okdam[0] && okdam[1])
+ RpAtomicSetRenderCallBack(okdam[1], RpAtomicGetRenderCallBack(okdam[0]));
+ }
+
+ SetVehicleComponentFlags(assoc.frame, desc[i].flags);
+
+ if(desc[i].flags & VEHICLE_FLAG_ADD_WHEEL){
+ if(m_wheelId == -1)
+ RwFrameDestroy(assoc.frame);
+ else{
+ RwV3d scale;
+ atomic = (RpAtomic*)CModelInfo::GetModelInfo(m_wheelId)->CreateInstance();
+ RwFrameDestroy(RpAtomicGetFrame(atomic));
+ RpAtomicSetFrame(atomic, assoc.frame);
+ RpClumpAddAtomic(m_clump, atomic);
+ CVisibilityPlugins::SetAtomicRenderCallback(atomic,
+ CVisibilityPlugins::RenderWheelAtomicCB);
+ scale.x = m_wheelScale;
+ scale.y = m_wheelScale;
+ scale.z = m_wheelScale;
+ RwFrameScale(assoc.frame, &scale, rwCOMBINEPRECONCAT);
+ }
+ }
+ }
+}
+
+
+#define COMPRULE_RULE(comprule) (((comprule) >> 12) & 0xF)
+#define COMPRULE_COMPS(comprule) ((comprule) & 0xFFF)
+#define COMPRULE_COMPN(comps, n) (((comps) >> 4*(n)) & 0xF)
+#define COMPRULE2_RULE(comprule) (((comprule) >> (12+16)) & 0xF)
+#define COMPRULE2_COMPS(comprule) ((comprule >> 16) & 0xFFF)
+#define COMPRULE2_COMPN(comps, n) (((comps >> 16) >> 4*(n)) & 0xF)
+
+bool
+IsValidCompRule(int rule)
+{
+ if(rule == 2)
+ return CWeather::OldWeatherType == WEATHER_RAINY ||
+ CWeather::NewWeatherType == WEATHER_RAINY;
+ return true;
+}
+
+int32
+CountCompsInRule(int comps)
+{
+ int32 n;
+ for(n = 0; comps != 0; comps >>= 4)
+ if((comps & 0xF) != 0xF)
+ n++;
+ return n;
+}
+
+int32
+ChooseComponent(int32 rule, int32 comps)
+{
+ int32 n;
+ switch(rule){
+ // identical cases....
+ case 1:
+ n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps));
+ return COMPRULE_COMPN(comps, n);
+ case 2:
+ // only valid in rain
+ n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps));
+ return COMPRULE_COMPN(comps, n);
+ }
+ return -1;
+}
+
+int32
+GetListOfComponentsNotUsedByRules(uint32 comprules, int32 numComps, int32 *comps)
+{
+ int32 i, n;
+ int32 unused[6] = { 0, 1, 2, 3, 4, 5 };
+
+ // first comprule
+ if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules)))
+ for(i = 0; i < 3; i++){
+ n = COMPRULE_COMPN(comprules, i);
+ if(n != 0xF)
+ unused[n] = 0xF;
+ }
+ // second comprule
+ comprules >>= 16;
+ if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules)))
+ for(i = 0; i < 3; i++){
+ n = COMPRULE_COMPN(comprules, i);
+ if(n != 0xF)
+ unused[n] = 0xF;
+ }
+
+ n = 0;
+ for(i = 0; i < numComps; i++)
+ if(unused[i] != 0xF)
+ comps[n++] = unused[i];
+ return n;
+}
+
+int32 wheelIds[] = { CAR_WHEEL_LF, CAR_WHEEL_LB, CAR_WHEEL_RF, CAR_WHEEL_RB };
+
+void
+CVehicleModelInfo::GetWheelPosn(int32 n, CVector &pos)
+{
+ RwMatrix *m = RwFrameGetMatrix(GetFrameFromId(m_clump, wheelIds[n]));
+ pos.x = RwMatrixGetPos(m)->x;
+ pos.y = RwMatrixGetPos(m)->y;
+ pos.z = RwMatrixGetPos(m)->z;
+}
+
+
+int32
+CVehicleModelInfo::ChooseComponent(void)
+{
+ int32 comp;
+ int32 comps[8];
+ int32 n;
+
+ comp = -1;
+ if(ms_compsToUse[0] == -2){
+ if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules)))
+ comp = ::ChooseComponent(COMPRULE_RULE(m_compRules), COMPRULE_COMPS(m_compRules));
+ else if(CGeneral::GetRandomNumberInRange(0, 3) < 2){
+ n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps);
+ if(n)
+ comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)];
+ }
+ }else{
+ comp = ms_compsToUse[0];
+ ms_compsToUse[0] = -2;
+ }
+ return comp;
+}
+
+int32
+CVehicleModelInfo::ChooseSecondComponent(void)
+{
+ int32 comp;
+ int32 comps[8];
+ int32 n;
+
+ comp = -1;
+ if(ms_compsToUse[1] == -2){
+ if(COMPRULE2_RULE(m_compRules) && IsValidCompRule(COMPRULE2_RULE(m_compRules)))
+ comp = ::ChooseComponent(COMPRULE2_RULE(m_compRules), COMPRULE2_COMPS(m_compRules));
+ else if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules)) &&
+ CGeneral::GetRandomNumberInRange(0, 3) < 2){
+
+ n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps);
+ if(n)
+ comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)];
+ }
+ }else{
+ comp = ms_compsToUse[1];
+ ms_compsToUse[1] = -2;
+ }
+ return comp;
+}
+
+struct editableMatCBData
+{
+ CVehicleModelInfo *vehicle;
+ int32 numMats1;
+ int32 numMats2;
+};
+
+RpMaterial*
+CVehicleModelInfo::GetEditableMaterialListCB(RpMaterial *material, void *data)
+{
+ static RwRGBA white = { 255, 255, 255, 255 };
+ RwRGBA *col;
+ editableMatCBData *cbdata;
+
+ cbdata = (editableMatCBData*)data;
+ col = RpMaterialGetColor(material);
+ if(col->red == 0x3C && col->green == 0xFF && col->blue == 0){
+ cbdata->vehicle->m_materials1[cbdata->numMats1++] = material;
+ RpMaterialSetColor(material, &white);
+ }else if(col->red == 0xFF && col->green == 0 && col->blue == 0xAF){
+ cbdata->vehicle->m_materials2[cbdata->numMats2++] = material;
+ RpMaterialSetColor(material, &white);
+ }
+ return material;
+}
+
+RpAtomic*
+CVehicleModelInfo::GetEditableMaterialListCB(RpAtomic *atomic, void *data)
+{
+ RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetEditableMaterialListCB, data);
+ return atomic;
+}
+
+void
+CVehicleModelInfo::FindEditableMaterialList(void)
+{
+ editableMatCBData cbdata;
+ int32 i;
+
+ cbdata.vehicle = this;
+ cbdata.numMats1 = 0;
+ cbdata.numMats2 = 0;
+ RpClumpForAllAtomics(m_clump, GetEditableMaterialListCB, &cbdata);
+ for(i = 0; i < m_numComps; i++)
+ GetEditableMaterialListCB(m_comps[i], &cbdata);
+ m_materials1[cbdata.numMats1] = nil;
+ m_materials2[cbdata.numMats2] = nil;
+ m_currentColour1 = -1;
+ m_currentColour2 = -1;
+}
+
+void
+CVehicleModelInfo::SetVehicleColour(uint8 c1, uint8 c2)
+{
+ RwRGBA col, *colp;
+ RwTexture *coltex;
+ RpMaterial **matp;
+
+ if(c1 != m_currentColour1){
+ col = ms_vehicleColourTable[c1];
+ coltex = ms_colourTextureTable[c1];
+ for(matp = m_materials1; *matp; matp++){
+ if(RpMaterialGetTexture(*matp) && RpMaterialGetTexture(*matp)->name[0] != '@'){
+ colp = RpMaterialGetColor(*matp);
+ colp->red = col.red;
+ colp->green = col.green;
+ colp->blue = col.blue;
+ }else
+ RpMaterialSetTexture(*matp, coltex);
+ }
+ m_currentColour1 = c1;
+ }
+
+ if(c2 != m_currentColour2){
+ col = ms_vehicleColourTable[c2];
+ coltex = ms_colourTextureTable[c2];
+ for(matp = m_materials2; *matp; matp++){
+ if(RpMaterialGetTexture(*matp) && RpMaterialGetTexture(*matp)->name[0] != '@'){
+ colp = RpMaterialGetColor(*matp);
+ colp->red = col.red;
+ colp->green = col.green;
+ colp->blue = col.blue;
+ }else
+ RpMaterialSetTexture(*matp, coltex);
+ }
+ m_currentColour2 = c2;
+ }
+}
+
+
+RpMaterial*
+CVehicleModelInfo::HasSpecularMaterialCB(RpMaterial *material, void *data)
+{
+ if(RpMaterialGetSurfaceProperties(material)->specular <= 0.0f)
+ return material;
+ *(bool*)data = true;
+ return nil;
+}
+
+RpMaterial*
+CVehicleModelInfo::SetEnvironmentMapCB(RpMaterial *material, void *data)
+{
+ float spec;
+
+ spec = RpMaterialGetSurfaceProperties(material)->specular;
+ if(spec <= 0.0f)
+ RpMatFXMaterialSetEffects(material, rpMATFXEFFECTNULL);
+ else{
+ if(RpMaterialGetTexture(material) == 0)
+ RpMaterialSetTexture(material, gpWhiteTexture);
+ RpMatFXMaterialSetEffects(material, rpMATFXEFFECTENVMAP);
+ spec *= 0.5f; // Tone down a bit for PC
+ RpMatFXMaterialSetupEnvMap(material, (RwTexture*)data, pMatFxIdentityFrame, false, spec);
+ }
+ return material;
+}
+
+RpAtomic*
+CVehicleModelInfo::SetEnvironmentMapCB(RpAtomic *atomic, void *data)
+{
+ bool hasSpec;
+ RpGeometry *geo;
+
+ geo = RpAtomicGetGeometry(atomic);
+ hasSpec = 0;
+ RpGeometryForAllMaterials(geo, HasSpecularMaterialCB, &hasSpec);
+ if(hasSpec){
+ RpGeometryForAllMaterials(geo, SetEnvironmentMapCB, data);
+ RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR);
+ RpMatFXAtomicEnableEffects(atomic);
+ // PS2 sets of PS2Manager lighting CB here
+ }
+ return atomic;
+}
+
+void
+CVehicleModelInfo::SetEnvironmentMap(void)
+{
+ CSimpleModelInfo *wheelmi;
+ int32 i;
+
+ if(pMatFxIdentityFrame == nil){
+ pMatFxIdentityFrame = RwFrameCreate();
+ RwMatrixSetIdentity(RwFrameGetMatrix(pMatFxIdentityFrame));
+ RwFrameUpdateObjects(pMatFxIdentityFrame);
+ RwFrameGetLTM(pMatFxIdentityFrame);
+ }
+
+ if(m_envMap != ms_pEnvironmentMaps[0]){
+ m_envMap = ms_pEnvironmentMaps[0];
+ RpClumpForAllAtomics(m_clump, SetEnvironmentMapCB, m_envMap);
+ if(m_wheelId != -1){
+ wheelmi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_wheelId);
+ for(i = 0; i < wheelmi->m_numAtomics; i++)
+ SetEnvironmentMapCB(wheelmi->m_atomics[i], m_envMap);
+ }
+ }
+}
+
+void
+CVehicleModelInfo::LoadEnvironmentMaps(void)
+{
+ char *texnames[] = {
+ "reflection01", // only one used
+ "reflection02",
+ "reflection03",
+ "reflection04",
+ "reflection05",
+ "reflection06",
+ };
+ int32 txdslot;
+ int32 i;
+
+ txdslot = CTxdStore::FindTxdSlot("particle");
+ CTxdStore::PushCurrentTxd();
+ CTxdStore::SetCurrentTxd(txdslot);
+ for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++){
+ ms_pEnvironmentMaps[i] = RwTextureRead(texnames[i], nil);
+ RwTextureSetFilterMode(ms_pEnvironmentMaps[i], rwFILTERLINEAR);
+ }
+ if(gpWhiteTexture == nil){
+ gpWhiteTexture = RwTextureRead("white", nil);
+ gpWhiteTexture->name[0] = '@';
+ RwTextureSetFilterMode(gpWhiteTexture, rwFILTERLINEAR);
+ }
+ CTxdStore::PopCurrentTxd();
+}
+
+void
+CVehicleModelInfo::ShutdownEnvironmentMaps(void)
+{
+ int32 i;
+
+ // ignoring "initialised" as that's a PS2 thing only
+ RwTextureDestroy(gpWhiteTexture);
+ gpWhiteTexture = nil;
+ for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++)
+ if(ms_pEnvironmentMaps[i])
+ RwTextureDestroy(ms_pEnvironmentMaps[i]);
+ RwFrameDestroy(pMatFxIdentityFrame);
+ pMatFxIdentityFrame = nil;
+}
+
+STARTPATCHES
+ InjectHook(0x51FDC0, &CVehicleModelInfo::DeleteRwObject_, PATCH_JUMP);
+ InjectHook(0x51FCB0, &CVehicleModelInfo::CreateInstance_, PATCH_JUMP);
+ InjectHook(0x51FC60, &CVehicleModelInfo::SetClump_, PATCH_JUMP);
+
+ InjectHook(0x51FE10, &CVehicleModelInfo::CollapseFramesCB, PATCH_JUMP);
+ InjectHook(0x51FE50, &CVehicleModelInfo::MoveObjectsCB, PATCH_JUMP);
+ InjectHook(0x51FE70, &CVehicleModelInfo::HideDamagedAtomicCB, PATCH_JUMP);
+ InjectHook(0x51FEF0, &CVehicleModelInfo::HasAlphaMaterialCB, PATCH_JUMP);
+
+ InjectHook(0x51FF10, &CVehicleModelInfo::SetAtomicRendererCB, PATCH_JUMP);
+ InjectHook(0x520030, &CVehicleModelInfo::SetAtomicRendererCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x520230, &CVehicleModelInfo::SetAtomicRendererCB_Train, PATCH_JUMP);
+ InjectHook(0x520120, &CVehicleModelInfo::SetAtomicRendererCB_Boat, PATCH_JUMP);
+ InjectHook(0x520210, &CVehicleModelInfo::SetAtomicRendererCB_Heli, PATCH_JUMP);
+ InjectHook(0x5202C0, &CVehicleModelInfo::SetAtomicRenderCallbacks, PATCH_JUMP);
+
+ InjectHook(0x520340, &CVehicleModelInfo::SetAtomicFlagCB, PATCH_JUMP);
+ InjectHook(0x520360, &CVehicleModelInfo::ClearAtomicFlagCB, PATCH_JUMP);
+
+ InjectHook(0x5204D0, &CVehicleModelInfo::PreprocessHierarchy, PATCH_JUMP);
+
+ InjectHook(0x520840, &CVehicleModelInfo::GetWheelPosn, PATCH_JUMP);
+
+ InjectHook(0x520880, IsValidCompRule, PATCH_JUMP);
+ InjectHook(0x520990, CountCompsInRule, PATCH_JUMP);
+ InjectHook(0x5209C0, ChooseComponent, PATCH_JUMP);
+ InjectHook(0x5208C0, GetListOfComponentsNotUsedByRules, PATCH_JUMP);
+ InjectHook(0x520AB0, &CVehicleModelInfo::ChooseComponent, PATCH_JUMP);
+ InjectHook(0x520BE0, &CVehicleModelInfo::ChooseSecondComponent, PATCH_JUMP);
+
+ InjectHook(0x520DC0, (RpAtomic *(*)(RpAtomic*, void*))CVehicleModelInfo::GetEditableMaterialListCB, PATCH_JUMP);
+ InjectHook(0x520D30, (RpMaterial *(*)(RpMaterial*, void*))CVehicleModelInfo::GetEditableMaterialListCB, PATCH_JUMP);
+ InjectHook(0x520DE0, &CVehicleModelInfo::FindEditableMaterialList, PATCH_JUMP);
+ InjectHook(0x520E70, &CVehicleModelInfo::SetVehicleColour, PATCH_JUMP);
+
+ InjectHook(0x521820, (RpAtomic *(*)(RpAtomic*, void*))CVehicleModelInfo::SetEnvironmentMapCB, PATCH_JUMP);
+ InjectHook(0x5217A0, (RpMaterial *(*)(RpMaterial*, void*))CVehicleModelInfo::SetEnvironmentMapCB, PATCH_JUMP);
+ InjectHook(0x521770, CVehicleModelInfo::HasSpecularMaterialCB, PATCH_JUMP);
+ InjectHook(0x521890, &CVehicleModelInfo::SetEnvironmentMap, PATCH_JUMP);
+ InjectHook(0x521680, CVehicleModelInfo::LoadEnvironmentMaps, PATCH_JUMP);
+ InjectHook(0x521720, CVehicleModelInfo::ShutdownEnvironmentMaps, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/modelinfo/VehicleModelInfo.h b/src/modelinfo/VehicleModelInfo.h
new file mode 100644
index 00000000..ccc46f73
--- /dev/null
+++ b/src/modelinfo/VehicleModelInfo.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include "ClumpModelInfo.h"
+
+enum {
+ NUM_VEHICLE_POSITIONS = 10,
+ NUM_FIRST_MATERIALS = 26,
+ NUM_SECOND_MATERIALS = 26,
+ NUM_VEHICLE_COLOURS = 8,
+ NUM_VEHICLE_ENVMAPS = 1
+};
+
+enum {
+ ATOMIC_FLAG_OK = 0x1,
+ ATOMIC_FLAG_DAM = 0x2,
+ ATOMIC_FLAG_LEFT = 0x4,
+ ATOMIC_FLAG_RIGHT = 0x8,
+ ATOMIC_FLAG_FRONT = 0x10,
+ ATOMIC_FLAG_REAR = 0x20,
+ ATOMIC_FLAG_DRAWLAST = 0x40,
+ ATOMIC_FLAG_WINDSCREEN = 0x80,
+ ATOMIC_FLAG_ANGLECULL = 0x100,
+ ATOMIC_FLAG_REARDOOR = 0x200,
+ ATOMIC_FLAG_FRONTDOOR = 0x400,
+ ATOMIC_FLAG_NOCULL = 0x800,
+};
+
+enum {
+ VEHICLE_TYPE_CAR,
+ VEHICLE_TYPE_BOAT,
+ VEHICLE_TYPE_TRAIN,
+ VEHICLE_TYPE_HELI,
+ VEHICLE_TYPE_PLANE,
+ VEHICLE_TYPE_BIKE,
+ NUM_VEHICLE_TYPES
+};
+
+class CVehicleModelInfo : public CClumpModelInfo
+{
+public:
+ uint8 m_lastColour1;
+ uint8 m_lastColour2;
+ char m_gameName[32];
+ int32 m_vehicleType;
+ int32 m_wheelId;
+ float m_wheelScale;
+ int32 m_numDoors;
+ int32 m_handlingId;
+ int32 m_vehicleClass;
+ int32 m_level;
+ CVector m_positions[NUM_VEHICLE_POSITIONS];
+ uint32 m_compRules;
+ float m_bikeSteerAngle;
+ RpMaterial *m_materials1[NUM_FIRST_MATERIALS];
+ RpMaterial *m_materials2[NUM_SECOND_MATERIALS];
+ uint8 m_colours1[NUM_VEHICLE_COLOURS];
+ uint8 m_colours2[NUM_VEHICLE_COLOURS];
+ uint8 m_numColours;
+ uint8 m_bLastColorVariation; //
+ uint8 m_currentColour1;
+ uint8 m_currentColour2;
+ RwTexture *m_envMap;
+ RpAtomic *m_comps[6];
+ int32 m_numComps;
+
+ static int8 *ms_compsToUse; // [2];
+ static int8 *ms_compsUsed; // [2];
+ static RwTexture **ms_pEnvironmentMaps; // [NUM_VEHICLE_ENVMAPS]
+ static RwRGBA *ms_vehicleColourTable; // [256]
+ static RwTexture **ms_colourTextureTable; // [256]
+ static RwObjectNameIdAssocation *ms_vehicleDescs[NUM_VEHICLE_TYPES];
+
+ CVehicleModelInfo(void);
+ void DeleteRwObject(void);
+ RwObject *CreateInstance(void);
+ void SetClump(RpClump *);
+
+ static RwFrame *CollapseFramesCB(RwFrame *frame, void *data);
+ static RwObject *MoveObjectsCB(RwObject *object, void *data);
+ static RpAtomic *HideDamagedAtomicCB(RpAtomic *atomic, void *data);
+ static RpMaterial *HasAlphaMaterialCB(RpMaterial *material, void *data);
+
+ static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data);
+ static RpAtomic *SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data);
+ static RpAtomic *SetAtomicRendererCB_Train(RpAtomic *atomic, void *data);
+ static RpAtomic *SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data);
+ static RpAtomic *SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data);
+ void SetAtomicRenderCallbacks(void);
+
+ static RpAtomic *SetAtomicFlagCB(RpAtomic *atomic, void *data);
+ static RpAtomic *ClearAtomicFlagCB(RpAtomic *atomic, void *data);
+ void SetVehicleComponentFlags(RwFrame *frame, uint32 flags);
+ void PreprocessHierarchy(void);
+ void GetWheelPosn(int32 n, CVector &pos);
+
+ int32 ChooseComponent(void);
+ int32 ChooseSecondComponent(void);
+
+ static RpMaterial *GetEditableMaterialListCB(RpMaterial *material, void *data);
+ static RpAtomic *GetEditableMaterialListCB(RpAtomic *atomic, void *data);
+ void FindEditableMaterialList(void);
+ void SetVehicleColour(uint8 c1, uint8 c2);
+
+ static RpAtomic *SetEnvironmentMapCB(RpAtomic *atomic, void *data);
+ static RpMaterial *SetEnvironmentMapCB(RpMaterial *material, void *data);
+ static RpMaterial *HasSpecularMaterialCB(RpMaterial *material, void *data);
+ void SetEnvironmentMap(void);
+ static void LoadEnvironmentMaps(void);
+ static void ShutdownEnvironmentMaps(void);
+
+ void DeleteRwObject_(void) { this->CVehicleModelInfo::DeleteRwObject(); }
+ RwObject *CreateInstance_(void) { return this->CVehicleModelInfo::CreateInstance(); }
+ void SetClump_(RpClump *clump) { this->CVehicleModelInfo::SetClump(clump); }
+};
+static_assert(sizeof(CVehicleModelInfo) == 0x1F8, "CVehicleModelInfo: error");
diff --git a/src/patcher.cpp b/src/patcher.cpp
new file mode 100644
index 00000000..5fdbdf8b
--- /dev/null
+++ b/src/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/patcher.h b/src/patcher.h
new file mode 100644
index 00000000..4ac1111b
--- /dev/null
+++ b/src/patcher.h
@@ -0,0 +1,171 @@
+#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
+
+enum
+{
+ PATCH_CALL,
+ PATCH_JUMP,
+ PATCH_NOTHING,
+};
+
+enum
+{
+ III_10 = 1,
+ III_11,
+ III_STEAM,
+ VC_10,
+ VC_11,
+ VC_STEAM
+};
+
+extern int gtaversion;
+
+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, typename HT> inline void
+InjectHook(AT address, HT hook, unsigned int nType=PATCH_NOTHING)
+{
+ 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);
+}
+
+
+
+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();
+};
+
+#define STARTPATCHES static StaticPatcher Patcher([](){
+#define ENDPATCHES });
diff --git a/src/render/2dEffect.h b/src/render/2dEffect.h
new file mode 100644
index 00000000..6cba85d1
--- /dev/null
+++ b/src/render/2dEffect.h
@@ -0,0 +1,39 @@
+class C2dEffect
+{
+public:
+ struct Light {
+ float dist;
+ float outerRange;
+ float size;
+ float innerRange;
+ uint8 flash;
+ uint8 wet;
+ uint8 flare;
+ uint8 shadowIntens;
+ uint8 flag;
+ RwTexture *corona;
+ RwTexture *shadow;
+ };
+ struct Particle {
+ int particleType;
+ float dir[3];
+ float scale;
+ };
+ struct Attractor {
+ CVector dir;
+ uint8 flag;
+ uint8 probability;
+ };
+
+ CVector pos;
+ RwRGBA col;
+ uint8 type;
+ union {
+ Light light;
+ Particle particle;
+ Attractor attractor;
+ };
+
+ C2dEffect(void) {}
+};
+static_assert(sizeof(C2dEffect) == 0x34, "C2dEffect: error");
diff --git a/src/render/Clouds.cpp b/src/render/Clouds.cpp
new file mode 100644
index 00000000..64bc93e8
--- /dev/null
+++ b/src/render/Clouds.cpp
@@ -0,0 +1,430 @@
+#include "common.h"
+#include "patcher.h"
+#include "Sprite.h"
+#include "General.h"
+#include "Coronas.h"
+#include "Camera.h"
+#include "TxdStore.h"
+#include "Weather.h"
+#include "Clock.h"
+#include "Timer.h"
+#include "Timecycle.h"
+#include "Renderer.h"
+#include "Clouds.h"
+
+#define SMALLSTRIPHEIGHT 4.0f
+#define HORIZSTRIPHEIGHT 48.0f
+
+RwTexture **gpCloudTex = (RwTexture**)0x9411C0; //[5];
+
+float &CClouds::CloudRotation = *(float*)0x8F5F40;
+uint32 &CClouds::IndividualRotation = *(uint32*)0x943078;
+
+float &CClouds::ms_cameraRoll = *(float*)0x8F29CC;
+float &CClouds::ms_horizonZ = *(float*)0x8F31C0;
+CRGBA &CClouds::ms_colourTop = *(CRGBA*)0x94143C;
+CRGBA &CClouds::ms_colourBottom = *(CRGBA*)0x8F2C38;
+
+void
+CClouds::Init(void)
+{
+ CTxdStore::PushCurrentTxd();
+ CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle"));
+ gpCloudTex[0] = RwTextureRead("cloud1", nil);
+ gpCloudTex[1] = RwTextureRead("cloud2", nil);
+ gpCloudTex[2] = RwTextureRead("cloud3", nil);
+ gpCloudTex[3] = RwTextureRead("cloudhilit", nil);
+ gpCloudTex[4] = RwTextureRead("cloudmasked", nil);
+ CTxdStore::PopCurrentTxd();
+ CloudRotation = 0.0f;
+}
+
+void
+CClouds::Update(void)
+{
+ float s = sin(TheCamera.Orientation - 0.85f);
+ CloudRotation += CWeather::Wind*s*0.0025f;
+ IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f) * 60.0f;
+}
+
+
+void
+CClouds::Render(void)
+{
+ int i;
+ float szx, szy;
+ RwV3d screenpos;
+ RwV3d worldpos;
+
+ CCoronas::SunBlockedByClouds = false;
+
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+ CSprite::InitSpriteBuffer();
+
+ int minute = CClock::GetHours()*60 + CClock::GetMinutes();
+ RwV3d campos = *(RwV3d*)&TheCamera.GetPosition();
+
+ float coverage = CWeather::CloudCoverage <= CWeather::Foggyness ? CWeather::Foggyness : CWeather::CloudCoverage;
+
+ // Moon
+ int moonfadeout = abs(minute - 180); // fully visible at 3AM
+ if(moonfadeout < 180){ // fade in/out 3 hours
+ int brightness = (1.0f - coverage) * (180 - moonfadeout);
+ RwV3d pos = { 0.0f, -100.0f, 15.0f };
+ RwV3dAdd(&worldpos, &campos, &pos);
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[2]->raster);
+ if(CCoronas::bSmallMoon){
+ szx *= 4.0f;
+ szy *= 4.0f;
+ }else{
+ szx *= 10.0f;
+ szy *= 10.0f;
+ }
+ CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z,
+ szx, szy, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255);
+ }
+ }
+
+ // The R* logo
+ int starintens = 0;
+ if(CClock::GetHours() < 22 && CClock::GetHours() > 5)
+ starintens = 0;
+ else if(CClock::GetHours() > 22 || CClock::GetHours() < 5)
+ starintens = 255;
+ else if(CClock::GetHours() == 22)
+ starintens = 255 * CClock::GetMinutes()/60.0f;
+ else if(CClock::GetHours() == 5)
+ starintens = 255 * (60 - CClock::GetMinutes())/60.0f;
+ if(starintens != 0){
+ // R
+ static float StarCoorsX[9] = { 0.0f, 0.05f, 0.12f, 0.5f, 0.8f, 0.6f, 0.27f, 0.55f, 0.75f };
+ static float StarCoorsY[9] = { 0.0f, 0.45f, 0.9f, 1.0f, 0.85f, 0.52f, 0.48f, 0.35f, 0.2f };
+ static float StarSizes[9] = { 1.0f, 1.4f, 0.9f, 1.0f, 0.6f, 1.5f, 1.3f, 1.0f, 0.8f };
+ int brightness = (1.0f - coverage) * starintens;
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster);
+ for(i = 0; i < 11; i++){
+ RwV3d pos = { 100.0f, 0.0f, 10.0f };
+ if(i >= 9) pos.x = -pos.x;
+ RwV3dAdd(&worldpos, &campos, &pos);
+ worldpos.y -= 90.0f*StarCoorsX[i%9];
+ worldpos.z += 80.0f*StarCoorsY[i%9];
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){
+ float sz = 0.8f*StarSizes[i%9];
+ CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z,
+ szx*sz, szy*sz, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255);
+ }
+ }
+ CSprite::FlushSpriteBuffer();
+
+ // *
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster);
+ RwV3d pos = { 100.0f, 0.0f, 10.0f };
+ RwV3dAdd(&worldpos, &campos, &pos);
+ worldpos.y -= 90.0f;
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){
+ brightness *= (CGeneral::GetRandomNumber()&127) / 640.0f + 0.5f;
+ CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z,
+ szx*5.0f, szy*5.0f, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255);
+ }
+ }
+
+ // Low clouds
+ static float LowCloudsX[12] = { 1.0f, 0.7f, 0.0f, -0.7f, -1.0f, -0.7f,
+ 0.0f, 0.7f, 0.8f, -0.8f, 0.4f, -0.4f };
+ static float LowCloudsY[12] = { 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f,
+ 1.0f, 0.7f, 0.4f, 0.4f, -0.8f, -0.8f };
+ static float LowCloudsZ[12] = { 0.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.3f,
+ 0.9f, 0.4f, 1.3f, 1.4f, 1.2f, 1.7f };
+ float lowcloudintensity = 1.0f - coverage;
+ int r = CTimeCycle::GetLowCloudsRed() * lowcloudintensity;
+ int g = CTimeCycle::GetLowCloudsGreen() * lowcloudintensity;
+ int b = CTimeCycle::GetLowCloudsBlue() * lowcloudintensity;
+ for(int cloudtype = 0; cloudtype < 3; cloudtype++){
+ for(i = cloudtype; i < 12; i += 3){
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[cloudtype]->raster);
+ RwV3d pos = { 800.0f*LowCloudsX[i], 800.0f*LowCloudsY[i], 60.0f*LowCloudsZ[i] };
+ worldpos.x = campos.x + pos.x;
+ worldpos.y = campos.y + pos.y;
+ worldpos.z = 40.0f + pos.z;
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false))
+ CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(screenpos.x, screenpos.y, screenpos.z,
+ szx*320.0f, szy*40.0f, r, g, b, 255, 1.0f/screenpos.z, ms_cameraRoll, 255);
+ }
+ CSprite::FlushSpriteBuffer();
+ }
+
+ // Fluffy clouds
+ float rot_sin = sin(CloudRotation);
+ float rot_cos = cos(CloudRotation);
+ int fluffyalpha = 160 * (1.0f - CWeather::Foggyness);
+ if(fluffyalpha != 0){
+ static float CoorsOffsetX[37] = {
+ 0.0f, 60.0f, 72.0f, 48.0f, 21.0f, 12.0f,
+ 9.0f, -3.0f, -8.4f, -18.0f, -15.0f, -36.0f,
+ -40.0f, -48.0f, -60.0f, -24.0f, 100.0f, 100.0f,
+ 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f,
+ 100.0f, 100.0f, -30.0f, -20.0f, 10.0f, 30.0f,
+ 0.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f
+ };
+ static float CoorsOffsetY[37] = {
+ 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f,
+ 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f,
+ 100.0f, 100.0f, 100.0f, 100.0f, -30.0f, 10.0f,
+ -25.0f, -5.0f, 28.0f, -10.0f, 10.0f, 0.0f,
+ 15.0f, 40.0f, -100.0f, -100.0f, -100.0f, -100.0f,
+ -100.0f, -40.0f, -20.0f, 0.0f, 10.0f, 30.0f, 35.0f
+ };
+ static float CoorsOffsetZ[37] = {
+ 2.0f, 1.0f, 0.0f, 0.3f, 0.7f, 1.4f,
+ 1.7f, 0.24f, 0.7f, 1.3f, 1.6f, 1.0f,
+ 1.2f, 0.3f, 0.7f, 1.4f, 0.0f, 0.1f,
+ 0.5f, 0.4f, 0.55f, 0.75f, 1.0f, 1.4f,
+ 1.7f, 2.0f, 2.0f, 2.3f, 1.9f, 2.4f,
+ 2.0f, 2.0f, 1.5f, 1.2f, 1.7f, 1.5f, 2.1f
+ };
+ static bool bCloudOnScreen[37];
+ float hilight;
+
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[4]->raster);
+ for(i = 0; i < 37; i++){
+ RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f };
+ worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x;
+ worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y;
+ worldpos.z = pos.z;
+
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){
+ float sundist = sqrt(sq(screenpos.x-CCoronas::SunScreenX) + sq(screenpos.y-CCoronas::SunScreenY));
+ int tr = CTimeCycle::GetFluffyCloudsTopRed();
+ int tg = CTimeCycle::GetFluffyCloudsTopGreen();
+ int tb = CTimeCycle::GetFluffyCloudsTopBlue();
+ int br = CTimeCycle::GetFluffyCloudsBottomRed();
+ int bg = CTimeCycle::GetFluffyCloudsBottomGreen();
+ int bb = CTimeCycle::GetFluffyCloudsBottomBlue();
+ if(sundist < SCREENW/2){
+ hilight = (1.0f - coverage) * (1.0f - sundist/(SCREENW/2));
+ tr = tr*(1.0f-hilight) + 255*hilight;
+ tg = tg*(1.0f-hilight) + 190*hilight;
+ tb = tb*(1.0f-hilight) + 190*hilight;
+ br = br*(1.0f-hilight) + 255*hilight;
+ bg = bg*(1.0f-hilight) + 190*hilight;
+ bb = bb*(1.0f-hilight) + 190*hilight;
+ if(sundist < SCREENW/10)
+ CCoronas::SunBlockedByClouds = true;
+ }else
+ hilight = 0.0f;
+ CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(screenpos.x, screenpos.y, screenpos.z,
+ szx*55.0f, szy*55.0f,
+ tr, tg, tb, br, bg, bb, 0.0f, -1.0f,
+ 1.0f/screenpos.z,
+ IndividualRotation/65336.0f * 2*3.14f + ms_cameraRoll,
+ fluffyalpha);
+ bCloudOnScreen[i] = true;
+ }else
+ bCloudOnScreen[i] = false;
+ }
+ CSprite::FlushSpriteBuffer();
+
+ // Highlights
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCloudTex[3]->raster);
+
+ for(i = 0; i < 37; i++){
+ RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f };
+ worldpos.x = campos.x*rot_cos + campos.y*rot_sin + pos.x;
+ worldpos.y = campos.x*rot_sin + campos.y*rot_cos + pos.y;
+ worldpos.z = pos.z;
+ if(bCloudOnScreen[i] && CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){
+ // BUG: this is stupid....would have to do this for each cloud individually
+ if(hilight > 0.0f){
+ CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(screenpos.x, screenpos.y, screenpos.z,
+ szx*30.0f, szy*30.0f,
+ 200*hilight, 0, 0, 255, 1.0f/screenpos.z,
+ 1.7f - CGeneral::GetATanOfXY(screenpos.x-CCoronas::SunScreenX, screenpos.y-CCoronas::SunScreenY) + CClouds::ms_cameraRoll, 255);
+ }
+ }
+ }
+ CSprite::FlushSpriteBuffer();
+ }
+
+ // Rainbow
+ if(CWeather::Rainbow != 0.0f){
+ static uint8 BowRed[6] = { 30, 30, 30, 10, 0, 15 };
+ static uint8 BowGreen[6] = { 0, 15, 30, 30, 0, 0 };
+ static uint8 BowBlue[6] = { 0, 0, 0, 10, 30, 30 };
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCoronaTexture[0]->raster);
+ for(i = 0; i < 6; i++){
+ RwV3d pos = { i*1.5f, 100.0f, 5.0f };
+ RwV3dAdd(&worldpos, &campos, &pos);
+ if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false))
+ CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z,
+ 2.0f*szx, 50.0*szy,
+ BowRed[i]*CWeather::Rainbow, BowGreen[i]*CWeather::Rainbow, BowBlue[i]*CWeather::Rainbow,
+ 255, 1.0f/screenpos.z, 255);
+
+ }
+ CSprite::FlushSpriteBuffer();
+ }
+
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+}
+
+bool
+UseDarkBackground(void)
+{
+ return RwFrameGetLTM(RwCameraGetFrame(TheCamera.m_pRwCamera))->up.z < -0.9f ||
+ gbShowCollisionPolys;
+}
+
+void
+CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue,
+ int16 botred, int16 botgreen, int16 botblue, int16 alpha)
+{
+ RwMatrix *mat = RwFrameGetLTM(RwCameraGetFrame(TheCamera.m_pRwCamera));
+ float c = sqrt(mat->right.x * mat->right.x + mat->right.y * mat->right.y);
+ if(c > 1.0f)
+ c = 1.0f;
+ ms_cameraRoll = acos(c);
+ if(mat->right.z < 0.0f)
+ ms_cameraRoll = -ms_cameraRoll;
+
+ if(UseDarkBackground()){
+ ms_colourTop.r = 50;
+ ms_colourTop.g = 50;
+ ms_colourTop.b = 50;
+ ms_colourTop.a = 255;
+ if(gbShowCollisionPolys){
+ if(CTimer::GetFrameCounter() & 1){
+ ms_colourTop.r = 0;
+ ms_colourTop.g = 0;
+ ms_colourTop.b = 0;
+ }else{
+ ms_colourTop.r = 255;
+ ms_colourTop.g = 255;
+ ms_colourTop.b = 255;
+ }
+ }
+ ms_colourBottom = ms_colourTop;
+ CRect r(0, 0, SCREENW, SCREENH);
+ CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop);
+ }else{
+ ms_horizonZ = CSprite::CalcHorizonCoors();
+
+ // Draw top/bottom gradient
+ float gradheight = SCREENH/2.0f;
+ float topedge = ms_horizonZ - gradheight;
+ float botpos, toppos;
+ if(ms_horizonZ > 0.0f && topedge < SCREENH){
+ ms_colourTop.r = topred;
+ ms_colourTop.g = topgreen;
+ ms_colourTop.b = topblue;
+ ms_colourTop.a = alpha;
+ ms_colourBottom.r = botred;
+ ms_colourBottom.g = botgreen;
+ ms_colourBottom.b = botblue;
+ ms_colourBottom.a = alpha;
+
+ if(ms_horizonZ < SCREENH)
+ botpos = ms_horizonZ;
+ else{
+ float f = (ms_horizonZ - SCREENH)/gradheight;
+ ms_colourBottom.r = topred*f + (1.0f-f)*botred;
+ ms_colourBottom.g = topgreen*f + (1.0f-f)*botgreen;
+ ms_colourBottom.b = topblue*f + (1.0f-f)*botblue;
+ botpos = SCREENH;
+ }
+ if(topedge >= 0.0f)
+ toppos = topedge;
+ else{
+ float f = (0.0f - topedge)/gradheight;
+ ms_colourTop.r = botred*f + (1.0f-f)*topred;
+ ms_colourTop.g = botgreen*f + (1.0f-f)*topgreen;
+ ms_colourTop.b = botblue*f + (1.0f-f)*topblue;
+ toppos = 0.0f;
+ }
+ CSprite2d::DrawRect(CRect(0, toppos, SCREENW, botpos),
+ ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop);
+ }
+
+ // draw the small stripe (whatever it's supposed to be)
+ if(ms_horizonZ > -SMALLSTRIPHEIGHT && ms_horizonZ < SCREENH){
+ // Same colour as fog
+ ms_colourTop.r = (topred + 2 * botred) / 3;
+ ms_colourTop.g = (topgreen + 2 * botgreen) / 3;
+ ms_colourTop.b = (topblue + 2 * botblue) / 3;
+ CSprite2d::DrawRect(CRect(0, ms_horizonZ, SCREENW, ms_horizonZ+SMALLSTRIPHEIGHT),
+ ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop);
+ }
+
+ // Only top
+ if(topedge > 0.0f){
+ ms_colourTop.r = topred;
+ ms_colourTop.g = topgreen;
+ ms_colourTop.b = topblue;
+ ms_colourTop.a = alpha;
+ ms_colourBottom.r = topred;
+ ms_colourBottom.g = topgreen;
+ ms_colourBottom.b = topblue;
+ ms_colourBottom.a = alpha;
+
+ botpos = min(SCREENH, topedge);
+ CSprite2d::DrawRect(CRect(0, 0, SCREENW, botpos),
+ ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop);
+ }
+
+ // Set both to fog colour for RenderHorizon
+ ms_colourTop.r = (topred + 2 * botred) / 3;
+ ms_colourTop.g = (topgreen + 2 * botgreen) / 3;
+ ms_colourTop.b = (topblue + 2 * botblue) / 3;
+ ms_colourBottom.r = (topred + 2 * botred) / 3;
+ ms_colourBottom.g = (topgreen + 2 * botgreen) / 3;
+ ms_colourBottom.b = (topblue + 2 * botblue) / 3;
+ }
+}
+
+void
+CClouds::RenderHorizon(void)
+{
+ if(UseDarkBackground())
+ return;
+
+ ms_colourBottom.a = 230;
+ ms_colourTop.a = 80;
+
+ if(ms_horizonZ > SCREENH)
+ return;
+
+ float z1 = min(ms_horizonZ + SMALLSTRIPHEIGHT, SCREENH);
+ CSprite2d::DrawRectXLU(CRect(0, ms_horizonZ, SCREENW, z1),
+ ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop);
+
+ // This is just weird
+ float a = SCREENH/400.0f * HORIZSTRIPHEIGHT +
+ SCREENH/300.0f * max(TheCamera.GetPosition().z, 0.0f);
+ float b = TheCamera.GetUp().z < 0.0f ?
+ SCREENH :
+ SCREENH * fabs(TheCamera.GetRight().z);
+ float z2 = z1 + (a + b)*TheCamera.LODDistMultiplier;
+ z2 = min(z2, SCREENH);
+ CSprite2d::DrawRect(CRect(0, z1, SCREENW, z2),
+ ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop);
+}
+
+STARTPATCHES
+ InjectHook(0x4F6C10, CClouds::Init, PATCH_JUMP);
+ InjectHook(0x4F6CE0, CClouds::Update, PATCH_JUMP);
+ InjectHook(0x4F6D90, CClouds::Render, PATCH_JUMP);
+ InjectHook(0x4F7F00, CClouds::RenderBackground, PATCH_JUMP);
+ InjectHook(0x4F85F0, CClouds::RenderHorizon, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/Clouds.h b/src/render/Clouds.h
new file mode 100644
index 00000000..96f04bb1
--- /dev/null
+++ b/src/render/Clouds.h
@@ -0,0 +1,20 @@
+#pragma once
+
+class CClouds
+{
+public:
+ static float &CloudRotation;
+ static uint32 &IndividualRotation;
+
+ static float &ms_cameraRoll;
+ static float &ms_horizonZ;
+ static CRGBA &ms_colourTop;
+ static CRGBA &ms_colourBottom;
+
+ static void Init(void);
+ static void Update(void);
+ static void Render(void);
+ static void RenderBackground(int16 topred, int16 topgreen, int16 topblue,
+ int16 botred, int16 botgreen, int16 botblue, int16 alpha);
+ static void RenderHorizon(void);
+};
diff --git a/src/render/Coronas.cpp b/src/render/Coronas.cpp
new file mode 100644
index 00000000..39b85246
--- /dev/null
+++ b/src/render/Coronas.cpp
@@ -0,0 +1,10 @@
+#include "common.h"
+#include "Coronas.h"
+
+RwTexture **gpCoronaTexture = (RwTexture**)0x5FAF44; //[9]
+
+float &CCoronas::LightsMult = *(float*)0x5FB088; // 1.0
+float &CCoronas::SunScreenX = *(float*)0x8F4358;
+float &CCoronas::SunScreenY = *(float*)0x8F4354;
+bool &CCoronas::bSmallMoon = *(bool*)0x95CD49;
+bool &CCoronas::SunBlockedByClouds = *(bool*)0x95CD73;
diff --git a/src/render/Coronas.h b/src/render/Coronas.h
new file mode 100644
index 00000000..4ec7dd3b
--- /dev/null
+++ b/src/render/Coronas.h
@@ -0,0 +1,13 @@
+#pragma once
+
+extern RwTexture **gpCoronaTexture; //[9]
+
+class CCoronas
+{
+public:
+ static float &LightsMult;
+ static float &SunScreenY;
+ static float &SunScreenX;
+ static bool &bSmallMoon;
+ static bool &SunBlockedByClouds;
+};
diff --git a/src/render/Draw.cpp b/src/render/Draw.cpp
new file mode 100644
index 00000000..f26f2ada
--- /dev/null
+++ b/src/render/Draw.cpp
@@ -0,0 +1,6 @@
+#include "common.h"
+#include "Draw.h"
+
+float &CDraw::ms_fNearClipZ = *(float*)0x8E2DC4;
+float &CDraw::ms_fFarClipZ = *(float*)0x9434F0;
+float &CDraw::ms_fFOV = *(float*)0x5FBC6C;
diff --git a/src/render/Draw.h b/src/render/Draw.h
new file mode 100644
index 00000000..62fe5193
--- /dev/null
+++ b/src/render/Draw.h
@@ -0,0 +1,16 @@
+#pragma once
+
+class CDraw
+{
+private:
+ static float &ms_fNearClipZ;
+ static float &ms_fFarClipZ;
+ static float &ms_fFOV;
+public:
+ static void SetNearClipZ(float nearclip) { ms_fNearClipZ = nearclip; }
+ static float GetNearClipZ(void) { return ms_fNearClipZ; }
+ static void SetFarClipZ(float farclip) { ms_fFarClipZ = farclip; }
+ static float GetFarClipZ(void) { return ms_fFarClipZ; }
+ static void SetFOV(float fov) { ms_fFOV = fov; }
+ static float GetFOV(void) { return ms_fFOV; }
+};
diff --git a/src/render/Lights.cpp b/src/render/Lights.cpp
new file mode 100644
index 00000000..6962af4d
--- /dev/null
+++ b/src/render/Lights.cpp
@@ -0,0 +1,171 @@
+#include "common.h"
+#include <rwcore.h>
+#include <rpworld.h>
+#include "patcher.h"
+#include "Lights.h"
+#include "Timecycle.h"
+#include "Coronas.h"
+#include "Weather.h"
+#include "CullZones.h"
+#include "MenuManager.h"
+
+RpLight *&pAmbient = *(RpLight**)0x885B6C;
+RpLight *&pDirect = *(RpLight**)0x880F7C;
+
+RwRGBAReal &AmbientLightColourForFrame = *(RwRGBAReal*)0x6F46F8;
+RwRGBAReal &AmbientLightColourForFrame_PedsCarsAndObjects = *(RwRGBAReal*)0x6F1D10;
+RwRGBAReal &DirectionalLightColourForFrame = *(RwRGBAReal*)0x87C6B8;
+
+RwRGBAReal &AmbientLightColour = *(RwRGBAReal*)0x86B0F8;
+RwRGBAReal &DirectionalLightColour = *(RwRGBAReal*)0x72E308;
+
+void
+SetLightsWithTimeOfDayColour(RpWorld *)
+{
+ CVector vec1, vec2, vecsun;
+ RwMatrix mat;
+
+ if(pAmbient){
+ AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed() * CCoronas::LightsMult;
+ AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen() * CCoronas::LightsMult;
+ AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue() * CCoronas::LightsMult;
+ if(CWeather::LightningFlash && !CCullZones::CamNoRain()){
+ AmbientLightColourForFrame.red = 1.0f;
+ AmbientLightColourForFrame.green = 1.0f;
+ AmbientLightColourForFrame.blue = 1.0f;
+ }
+ AmbientLightColourForFrame_PedsCarsAndObjects.red = min(1.0f, AmbientLightColourForFrame.red*1.3f);
+ AmbientLightColourForFrame_PedsCarsAndObjects.green = min(1.0f, AmbientLightColourForFrame.green*1.3f);
+ AmbientLightColourForFrame_PedsCarsAndObjects.blue = min(1.0f, AmbientLightColourForFrame.blue*1.3f);
+ RpLightSetColor(pAmbient, &AmbientLightColourForFrame);
+ }
+
+ if(pDirect){
+ DirectionalLightColourForFrame.red = CTimeCycle::GetDirectionalRed() * CCoronas::LightsMult;
+ DirectionalLightColourForFrame.green = CTimeCycle::GetDirectionalGreen() * CCoronas::LightsMult;
+ DirectionalLightColourForFrame.blue = CTimeCycle::GetDirectionalBlue() * CCoronas::LightsMult;
+ RpLightSetColor(pDirect, &DirectionalLightColourForFrame);
+
+ vecsun = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue];
+ vec1 = CVector(0.0f, 0.0f, 1.0f);
+ vec2 = CrossProduct(vec1, vecsun);
+ vec2.Normalise();
+ vec1 = CrossProduct(vec2, vecsun);
+ mat.at.x = -vecsun.x;
+ mat.at.y = -vecsun.y;
+ mat.at.z = -vecsun.z;
+ mat.right.x = vec1.x;
+ mat.right.y = vec1.y;
+ mat.right.z = vec1.z;
+ mat.up.x = vec2.x;
+ mat.up.y = vec2.y;
+ mat.up.z = vec2.z;
+ RwFrameTransform(RpLightGetFrame(pDirect), &mat, rwCOMBINEREPLACE);
+ }
+
+ if(CMenuManager::m_PrefsBrightness > 256){
+ float f1 = 2.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f;
+ float f2 = 3.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f;
+
+ AmbientLightColourForFrame.red = min(1.0f, AmbientLightColourForFrame.red * f2);
+ AmbientLightColourForFrame.green = min(1.0f, AmbientLightColourForFrame.green * f2);
+ AmbientLightColourForFrame.blue = min(1.0f, AmbientLightColourForFrame.blue * f2);
+ AmbientLightColourForFrame_PedsCarsAndObjects.red = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.red * f1);
+ AmbientLightColourForFrame_PedsCarsAndObjects.green = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.green * f1);
+ AmbientLightColourForFrame_PedsCarsAndObjects.blue = min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.blue * f1);
+#ifdef FIX_BUGS
+ DirectionalLightColourForFrame.red = min(1.0f, DirectionalLightColourForFrame.red * f1);
+ DirectionalLightColourForFrame.green = min(1.0f, DirectionalLightColourForFrame.green * f1);
+ DirectionalLightColourForFrame.blue = min(1.0f, DirectionalLightColourForFrame.blue * f1);
+#else
+ DirectionalLightColourForFrame.red = min(1.0f, AmbientLightColourForFrame.red * f1);
+ DirectionalLightColourForFrame.green = min(1.0f, AmbientLightColourForFrame.green * f1);
+ DirectionalLightColourForFrame.blue = min(1.0f, AmbientLightColourForFrame.blue * f1);
+#endif
+ }
+}
+
+void
+SetAmbientAndDirectionalColours(float f)
+{
+ AmbientLightColour.red = AmbientLightColourForFrame.red * f;
+ AmbientLightColour.green = AmbientLightColourForFrame.green * f;
+ AmbientLightColour.blue = AmbientLightColourForFrame.blue * f;
+
+ DirectionalLightColour.red = DirectionalLightColourForFrame.red * f;
+ DirectionalLightColour.green = DirectionalLightColourForFrame.green * f;
+ DirectionalLightColour.blue = DirectionalLightColourForFrame.blue * f;
+
+ RpLightSetColor(pAmbient, &AmbientLightColour);
+ RpLightSetColor(pDirect, &DirectionalLightColour);
+}
+
+void
+SetBrightMarkerColours(float f)
+{
+ AmbientLightColour.red = 0.6f;
+ AmbientLightColour.green = 0.6f;
+ AmbientLightColour.blue = 0.6f;
+
+ DirectionalLightColour.red = (1.0f - DirectionalLightColourForFrame.red) * 0.4f + DirectionalLightColourForFrame.red;
+ DirectionalLightColour.green = (1.0f - DirectionalLightColourForFrame.green) * 0.4f + DirectionalLightColourForFrame.green;
+ DirectionalLightColour.blue = (1.0f - DirectionalLightColourForFrame.blue) * 0.4f + DirectionalLightColourForFrame.blue;
+
+ RpLightSetColor(pAmbient, &AmbientLightColour);
+ RpLightSetColor(pDirect, &DirectionalLightColour);
+}
+
+void
+ReSetAmbientAndDirectionalColours(void)
+{
+ RpLightSetColor(pAmbient, &AmbientLightColourForFrame);
+ RpLightSetColor(pDirect, &DirectionalLightColourForFrame);
+}
+
+void
+DeActivateDirectional(void)
+{
+ RpLightSetFlags(pDirect, 0);
+}
+
+void
+ActivateDirectional(void)
+{
+ RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS);
+}
+
+void
+SetAmbientColours(void)
+{
+ RpLightSetColor(pAmbient, &AmbientLightColourForFrame);
+}
+
+void
+SetAmbientColoursForPedsCarsAndObjects(void)
+{
+ RpLightSetColor(pAmbient, &AmbientLightColourForFrame_PedsCarsAndObjects);
+}
+
+uint8 IndicateR[] = { 0, 255, 0, 0, 255, 255, 0 };
+uint8 IndicateG[] = { 0, 0, 255, 0, 255, 0, 255 };
+uint8 IndicateB[] = { 0, 0, 0, 255, 0, 255, 255 };
+
+void
+SetAmbientColoursToIndicateRoadGroup(int i)
+{
+ AmbientLightColour.red = IndicateR[i%7]/255.0f;
+ AmbientLightColour.green = IndicateG[i%7]/255.0f;
+ AmbientLightColour.blue = IndicateB[i%7]/255.0f;
+ RpLightSetColor(pAmbient, &AmbientLightColour);
+}
+
+STARTPATCHES
+ InjectHook(0x526510, SetLightsWithTimeOfDayColour, PATCH_JUMP);
+ InjectHook(0x526DE0, SetAmbientAndDirectionalColours, PATCH_JUMP);
+ InjectHook(0x526E60, SetBrightMarkerColours, PATCH_JUMP);
+ InjectHook(0x526F10, ReSetAmbientAndDirectionalColours, PATCH_JUMP);
+ InjectHook(0x526F40, DeActivateDirectional, PATCH_JUMP);
+ InjectHook(0x526F50, ActivateDirectional, PATCH_JUMP);
+ InjectHook(0x526F60, SetAmbientColours, PATCH_JUMP);
+ InjectHook(0x526F80, SetAmbientColoursForPedsCarsAndObjects, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/Lights.h b/src/render/Lights.h
new file mode 100644
index 00000000..ca926eb8
--- /dev/null
+++ b/src/render/Lights.h
@@ -0,0 +1,9 @@
+void SetLightsWithTimeOfDayColour(RpWorld *);
+void SetAmbientAndDirectionalColours(float f);
+void SetBrightMarkerColours(float f);
+void ReSetAmbientAndDirectionalColours(void);
+void DeActivateDirectional(void);
+void ActivateDirectional(void);
+void SetAmbientColours(void);
+void SetAmbientColoursForPedsCarsAndObjects(void);
+void SetAmbientColoursToIndicateRoadGroup(int i); \ No newline at end of file
diff --git a/src/render/Particle.cpp b/src/render/Particle.cpp
new file mode 100644
index 00000000..9bbc4587
--- /dev/null
+++ b/src/render/Particle.cpp
@@ -0,0 +1,10 @@
+#include "common.h"
+#include "patcher.h"
+#include "Particle.h"
+
+WRAPPER void
+CParticle::AddParticle(tParticleType, const CVector &pos, const CVector &velocity, CEntity *ent,
+ float size, int32 rotationStep, int32 rotation, int startFrame, int lifeSpan)
+{
+ EAXJMP(0x50D140);
+}
diff --git a/src/render/Particle.h b/src/render/Particle.h
new file mode 100644
index 00000000..f711ecf1
--- /dev/null
+++ b/src/render/Particle.h
@@ -0,0 +1,82 @@
+#pragma once
+
+enum tParticleType
+{
+ PARTICLE_SPARK,
+ PARTICLE_SPARK_SMALL,
+ PARTICLE_WHEEL_DIRT,
+ PARTICLE_WHEEL_WATER,
+ PARTICLE_BLOOD,
+ PARTICLE_BLOOD_SMALL,
+ PARTICLE_BLOOD_SPURT,
+ PARTICLE_DEBRIS,
+ PARTICLE_DEBRIS2,
+ PARTICLE_WATER,
+ PARTICLE_FLAME,
+ PARTICLE_FIREBALL,
+ PARTICLE_GUNFLASH,
+ PARTICLE_GUNFLASH_NOANIM,
+ PARTICLE_GUNSMOKE,
+ PARTICLE_GUNSMOKE2,
+ PARTICLE_SMOKE,
+ PARTICLE_SMOKE_SLOWMOTION,
+ PARTICLE_GARAGEPAINT_SPRAY,
+ PARTICLE_SHARD,
+ PARTICLE_SPLASH,
+ PARTICLE_CARFLAME,
+ PARTICLE_STEAM,
+ PARTICLE_STEAM2,
+ PARTICLE_STEAM_NY,
+ PARTICLE_STEAM_NY_SLOWMOTION,
+ PARTICLE_ENGINE_STEAM,
+ PARTICLE_RAINDROP,
+ PARTICLE_RAINDROP_SMALL,
+ PARTICLE_RAIN_SPLASH,
+ PARTICLE_RAIN_SPLASH_BIGGROW,
+ PARTICLE_RAIN_SPLASHUP,
+ PARTICLE_WATERSPRAY,
+ PARTICLE_EXPLOSION_MEDIUM,
+ PARTICLE_EXPLOSION_LARGE,
+ PARTICLE_EXPLOSION_MFAST,
+ PARTICLE_EXPLOSION_LFAST,
+ PARTICLE_CAR_SPLASH,
+ PARTICLE_BOAT_SPLASH,
+ PARTICLE_BOAT_THRUSTJET,
+ PARTICLE_BOAT_WAKE,
+ PARTICLE_WATER_HYDRANT,
+ PARTICLE_WATER_CANNON,
+ PARTICLE_EXTINGUISH_STEAM,
+ PARTICLE_PED_SPLASH,
+ PARTICLE_PEDFOOT_DUST,
+ PARTICLE_HELI_DUST,
+ PARTICLE_HELI_ATTACK,
+ PARTICLE_ENGINE_SMOKE,
+ PARTICLE_ENGINE_SMOKE2,
+ PARTICLE_CARFLAME_SMOKE,
+ PARTICLE_FIREBALL_SMOKE,
+ PARTICLE_PAINT_SMOKE,
+ PARTICLE_TREE_LEAVES,
+ PARTICLE_CARCOLLISION_DUST,
+ PARTICLE_CAR_DEBRIS,
+ PARTICLE_HELI_DEBRIS,
+ PARTICLE_EXHAUST_FUMES,
+ PARTICLE_RUBBER_SMOKE,
+ PARTICLE_BURNINGRUBBER_SMOKE,
+ PARTICLE_BULLETHIT_SMOKE,
+ PARTICLE_GUNSHELL_FIRST,
+ PARTICLE_GUNSHELL,
+ PARTICLE_GUNSHELL_BUMP1,
+ PARTICLE_GUNSHELL_BUMP2,
+ PARTICLE_TEST,
+ PARTICLE_BIRD_FRONT,
+ PARTICLE_RAINDROP_2D,
+};
+
+class CEntity;
+
+class CParticle
+{
+public:
+ static void AddParticle(tParticleType, const CVector &pos, const CVector &velocity, CEntity *ent = nil,
+ float size = 0.0, int32 rotationStep = 0, int32 rotation = 0, int startFrame = 0, int lifeSpan = 0);
+};
diff --git a/src/render/RenderBuffer.cpp b/src/render/RenderBuffer.cpp
new file mode 100644
index 00000000..9a1ed58d
--- /dev/null
+++ b/src/render/RenderBuffer.cpp
@@ -0,0 +1,59 @@
+#include "common.h"
+#include "patcher.h"
+#include "RenderBuffer.h"
+
+int32 &TempBufferVerticesStored = *(int32*)0x8F5F78;
+int32 &TempBufferIndicesStored = *(int32*)0x8F1A4C;
+
+RwIm3DVertex *TempVertexBuffer = (RwIm3DVertex*)0x862330;
+RwImVertexIndex *TempBufferRenderIndexList = (RwImVertexIndex*)0x846288;
+
+int RenderBuffer::VerticesToBeStored;
+int RenderBuffer::IndicesToBeStored;
+
+void
+RenderBuffer::ClearRenderBuffer(void)
+{
+ TempBufferVerticesStored = 0;
+ TempBufferIndicesStored = 0;
+}
+
+void
+RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart)
+{
+ if(TempBufferIndicesStored + numIndices >= 1024)
+ RenderStuffInBuffer();
+ if(TempBufferVerticesStored + numVertices >= 256)
+ RenderStuffInBuffer();
+ *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored];
+ *vertexStart = &TempVertexBuffer[TempBufferVerticesStored];
+ IndicesToBeStored = numIndices;
+ VerticesToBeStored = numVertices;
+}
+
+void
+RenderBuffer::StopStoring(void)
+{
+ int i;
+ for(i = TempBufferIndicesStored; i < TempBufferIndicesStored+IndicesToBeStored; i++)
+ TempBufferRenderIndexList[i] += TempBufferVerticesStored;
+ TempBufferIndicesStored += IndicesToBeStored;
+ TempBufferVerticesStored += VerticesToBeStored;
+}
+
+void
+RenderBuffer::RenderStuffInBuffer(void)
+{
+ if(TempBufferVerticesStored && RwIm3DTransform(TempVertexBuffer, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
+ RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
+ RwIm3DEnd();
+ }
+ ClearRenderBuffer();
+}
+
+STARTPATCHES
+ InjectHook(0x517620, RenderBuffer::ClearRenderBuffer, PATCH_JUMP);
+ InjectHook(0x517640, RenderBuffer::StartStoring, PATCH_JUMP);
+ InjectHook(0x5176B0, RenderBuffer::StopStoring, PATCH_JUMP);
+ InjectHook(0x5177C0, RenderBuffer::RenderStuffInBuffer, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/RenderBuffer.h b/src/render/RenderBuffer.h
new file mode 100644
index 00000000..66baa2d0
--- /dev/null
+++ b/src/render/RenderBuffer.h
@@ -0,0 +1,10 @@
+class RenderBuffer
+{
+public:
+ static int VerticesToBeStored;
+ static int IndicesToBeStored;
+ static void ClearRenderBuffer(void);
+ static void StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart);
+ static void StopStoring(void);
+ static void RenderStuffInBuffer(void);
+};
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
new file mode 100644
index 00000000..fd949cee
--- /dev/null
+++ b/src/render/Renderer.cpp
@@ -0,0 +1,1165 @@
+#include "common.h"
+#include "patcher.h"
+#include "Lights.h"
+#include "ModelInfo.h"
+#include "Treadable.h"
+#include "Ped.h"
+#include "Vehicle.h"
+#include "Object.h"
+#include "PathFind.h"
+#include "Collision.h"
+#include "VisibilityPlugins.h"
+#include "Clock.h"
+#include "World.h"
+#include "Camera.h"
+#include "ModelIndices.h"
+#include "Streaming.h"
+#include "Renderer.h"
+
+bool gbShowPedRoadGroups;
+bool gbShowCarRoadGroups;
+bool gbShowCollisionPolys;
+
+bool gbDontRenderBuildings;
+bool gbDontRenderBigBuildings;
+bool gbDontRenderPeds;
+bool gbDontRenderObjects;
+
+struct EntityInfo
+{
+ CEntity *ent;
+ float sort;
+};
+
+CLinkList<EntityInfo> &gSortedVehiclesAndPeds = *(CLinkList<EntityInfo>*)0x629AC0;
+
+int32 &CRenderer::ms_nNoOfVisibleEntities = *(int32*)0x940730;
+CEntity **CRenderer::ms_aVisibleEntityPtrs = (CEntity**)0x6E9920;
+int32 &CRenderer::ms_nNoOfInVisibleEntities = *(int32*)0x8F1B78;
+CEntity **CRenderer::ms_aInVisibleEntityPtrs = (CEntity**)0x880B50;
+
+CVector &CRenderer::ms_vecCameraPosition = *(CVector*)0x8E2C3C;
+CVehicle *&CRenderer::m_pFirstPersonVehicle = *(CVehicle**)0x885B80;
+bool &CRenderer::m_loadingPriority = *(bool*)0x95CD86;
+
+void
+CRenderer::Init(void)
+{
+ gSortedVehiclesAndPeds.Init(40);
+ SortBIGBuildings();
+}
+void
+CRenderer::RenderOneRoad(CEntity *e)
+{
+ if(gbDontRenderBuildings)
+ return;
+ if(gbShowCollisionPolys)
+ CCollision::DrawColModel_Coloured(e->GetMatrix(),
+ *CModelInfo::GetModelInfo(e->m_modelIndex)->GetColModel(),
+ e->m_modelIndex);
+ else
+ e->Render();
+}
+
+void
+CRenderer::RenderOneNonRoad(CEntity *e)
+{
+ CPed *ped;
+ CVehicle *veh;
+ int i;
+ bool resetLights;
+
+#ifndef MASTER
+ if(gbShowCollisionPolys){
+ if(!e->IsVehicle()){
+ CCollision::DrawColModel_Coloured(e->GetMatrix(),
+ *CModelInfo::GetModelInfo(e->m_modelIndex)->GetColModel(),
+ e->m_modelIndex);
+ return;
+ }
+ }else if(e->IsBuilding()){
+ if(e->bIsBIGBuilding){
+ if(gbDontRenderBigBuildings)
+ return;
+ }else{
+ if(gbDontRenderBuildings)
+ return;
+ }
+ }else
+#endif
+ if(e->IsPed()){
+#ifndef MASTER
+ if(gbDontRenderPeds)
+ return;
+#endif
+ ped = (CPed*)e;
+ if(ped->m_nPedState == PED_PASSENGER)
+ return;
+ }
+#ifndef MASTER
+ else if(e->IsObject() || e->IsDummy()){
+ if(gbDontRenderObjects)
+ return;
+ }
+#endif
+
+ resetLights = e->SetupLighting();
+
+ if(e->IsVehicle())
+ CVisibilityPlugins::InitAlphaAtomicList();
+
+ // Render Peds in vehicle before vehicle itself
+ if(e->IsVehicle()){
+ veh = (CVehicle*)e;
+ if(veh->pDriver && veh->pDriver->m_nPedState == PED_PASSENGER)
+ veh->pDriver->Render();
+ for(i = 0; i < 8; i++)
+ if(veh->pPassengers[i] && veh->pPassengers[i]->m_nPedState == PED_PASSENGER)
+ veh->pPassengers[i]->Render();
+ }
+ e->Render();
+
+ if(e->IsVehicle()){
+ e->bImBeingRendered = true;
+ CVisibilityPlugins::RenderAlphaAtomics();
+ e->bImBeingRendered = false;
+ }
+
+ e->RemoveLighting(resetLights);
+}
+
+void
+CRenderer::RenderFirstPersonVehicle(void)
+{
+ if(m_pFirstPersonVehicle == nil)
+ return;
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RenderOneNonRoad(m_pFirstPersonVehicle);
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
+}
+
+void
+CRenderer::RenderRoads(void)
+{
+ int i;
+ CTreadable *t;
+
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
+ DeActivateDirectional();
+ SetAmbientColours();
+
+ ThePaths.m_pathNodes[-1].group = 6;
+
+ for(i = 0; i < ms_nNoOfVisibleEntities; i++){
+ t = (CTreadable*)ms_aVisibleEntityPtrs[i];
+ if(t->IsBuilding() && t->GetIsATreadable()){
+#ifndef MASTER
+ if(gbShowCarRoadGroups || gbShowPedRoadGroups){
+ int ind = 0;
+ if(gbShowCarRoadGroups)
+ ind += ThePaths.m_pathNodes[t->m_nodeIndicesCars[0]].group;
+ if(gbShowPedRoadGroups)
+ ind += ThePaths.m_pathNodes[t->m_nodeIndicesPeds[0]].group;
+ SetAmbientColoursToIndicateRoadGroup(ind);
+ }
+#endif
+ RenderOneRoad(t);
+#ifndef MASTER
+ if(gbShowCarRoadGroups || gbShowPedRoadGroups)
+ ReSetAmbientAndDirectionalColours();
+#endif
+ }
+ }
+}
+
+void
+CRenderer::RenderEverythingBarRoads(void)
+{
+ int i;
+ CEntity *e;
+ CVector dist;
+ EntityInfo ei;
+
+ gSortedVehiclesAndPeds.Clear();
+
+ for(i = 0; i < ms_nNoOfVisibleEntities; i++){
+ e = ms_aVisibleEntityPtrs[i];
+
+ if(e->IsBuilding() && ((CBuilding*)e)->GetIsATreadable())
+ continue;
+
+ if(e->IsVehicle() ||
+ e->IsPed() && CVisibilityPlugins::GetClumpAlpha((RpClump*)e->m_rwObject) != 255){
+ if(e->IsVehicle() && ((CVehicle*)e)->m_vehType == VEHICLE_TYPE_BOAT){
+ dist = ms_vecCameraPosition - e->GetPosition();
+ if(!CVisibilityPlugins::InsertEntityIntoSortedList(e, dist.Magnitude())){
+ printf("Ran out of space in alpha entity list");
+ RenderOneNonRoad(e);
+ }
+ }else{
+ ei.ent = e;
+ dist = ms_vecCameraPosition - e->GetPosition();
+ ei.sort = dist.MagnitudeSqr();
+ gSortedVehiclesAndPeds.InsertSorted(ei);
+ }
+ }else
+ RenderOneNonRoad(e);
+ }
+}
+
+void
+CRenderer::RenderVehiclesButNotBoats(void)
+{
+ CLink<EntityInfo> *node;
+
+ for(node = gSortedVehiclesAndPeds.tail.prev;
+ node != &gSortedVehiclesAndPeds.head;
+ node = node->prev){
+ CVehicle *v = (CVehicle*)node->item.ent;
+ if(v->IsVehicle() && v->m_vehType == VEHICLE_TYPE_BOAT) // BUG: missing in III
+ continue;
+ RenderOneNonRoad(v);
+ }
+}
+
+void
+CRenderer::RenderBoats(void)
+{
+ CLink<EntityInfo> *node;
+
+ for(node = gSortedVehiclesAndPeds.tail.prev;
+ node != &gSortedVehiclesAndPeds.head;
+ node = node->prev){
+ CVehicle *v = (CVehicle*)node->item.ent;
+ if(!v->IsVehicle()) // BUG: missing in III
+ continue;
+ if(v->m_vehType == VEHICLE_TYPE_BOAT)
+ RenderOneNonRoad(v);
+ }
+}
+
+void
+CRenderer::RenderFadingInEntities(void)
+{
+ RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
+ DeActivateDirectional();
+ SetAmbientColours();
+ CVisibilityPlugins::RenderFadingEntities();
+}
+
+enum Visbility
+{
+ VIS_INVISIBLE,
+ VIS_VISIBLE,
+ VIS_OFFSCREEN,
+ VIS_STREAMME
+};
+
+#define LOD_DISTANCE 300.0f
+#define FADE_DISTANCE 20.0f
+#define STREAM_DISTANCE 30.0f
+
+// Time Objects can be time culled if
+// other == -1 || CModelInfo::GetModelInfo(other)->GetRwObject()
+// i.e. we have to draw even at the wrong time if
+// other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil
+
+#define OTHERUNAVAILABLE (other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil)
+#define CANTIMECULL (!OTHERUNAVAILABLE)
+
+int32
+CRenderer::SetupEntityVisibility(CEntity *ent)
+{
+ CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex);
+ CTimeModelInfo *ti;
+ int32 other;
+ float dist;
+
+ bool request = true;
+ if(mi->m_type == MITYPE_TIME){
+ ti = (CTimeModelInfo*)mi;
+ other = ti->GetOtherTimeModel();
+ if(CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())){
+ // don't fade in, or between time objects
+ if(CANTIMECULL)
+ ti->m_alpha = 255;
+ }else{
+ // Hide if possible
+ if(CANTIMECULL)
+ return VIS_INVISIBLE;
+ // can't cull, so we'll try to draw this one, but don't request
+ // it since what we really want is the other one.
+ request = false;
+ }
+ }else{
+ if(mi->m_type != MITYPE_SIMPLE){
+ if(FindPlayerVehicle() == ent &&
+ TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON){
+ // Player's vehicle in first person mode
+ if(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD ||
+ ent->GetModelIndex() == MI_RHINO ||
+ ent->GetModelIndex() == MI_COACH ||
+ TheCamera.m_bInATunnelAndABigVehicle){
+ ent->m_flagD40 = true;
+ }else{
+ m_pFirstPersonVehicle = (CVehicle*)ent;
+ ent->m_flagD40 = false;
+ }
+ return VIS_OFFSCREEN;
+ }else{
+ // All sorts of Clumps
+ if(ent->m_rwObject == nil || !ent->bIsVisible)
+ return VIS_INVISIBLE;
+ if(!ent->GetIsOnScreen())
+ return VIS_OFFSCREEN;
+ if(ent->bDrawLast){
+ dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude();
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ ent->bDistanceFade = false;
+ return VIS_INVISIBLE;
+ }else
+ return VIS_VISIBLE;
+ }
+ return VIS_INVISIBLE;
+ }
+ if(ent->m_type == ENTITY_TYPE_OBJECT &&
+ ((CObject*)ent)->ObjectCreatedBy == TEMP_OBJECT){
+ if(ent->m_rwObject == nil || !ent->bIsVisible)
+ return VIS_INVISIBLE;
+ return ent->GetIsOnScreen() ? VIS_VISIBLE : VIS_OFFSCREEN;
+ }
+ }
+
+ // Simple ModelInfo
+
+ dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude();
+
+ // This can only happen with multi-atomic models (e.g. railtracks)
+ // but why do we bump up the distance? can only be fading...
+ if(LOD_DISTANCE + STREAM_DISTANCE < dist && dist < mi->GetLargestLodDistance())
+ dist = mi->GetLargestLodDistance();
+
+ if(ent->m_type == ENTITY_TYPE_OBJECT && ent->bRenderDamaged)
+ mi->m_isDamaged = true;
+
+ RpAtomic *a = mi->GetAtomicFromDistance(dist);
+ if(a){
+ mi->m_isDamaged = 0;
+ if(ent->m_rwObject == nil)
+ ent->CreateRwObject();
+ assert(ent->m_rwObject);
+ RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject;
+ // Make sure our atomic uses the right geometry and not
+ // that of an atomic for another draw distance.
+ if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj))
+ RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0);
+ mi->IncreaseAlpha();
+ if(ent->m_rwObject == nil || !ent->bIsVisible)
+ return VIS_INVISIBLE;
+
+ if(!ent->GetIsOnScreen()){
+ mi->m_alpha = 255;
+ return VIS_OFFSCREEN;
+ }
+
+ if(mi->m_alpha != 255){
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ ent->bDistanceFade = true;
+ return VIS_INVISIBLE;
+ }
+
+ if(mi->m_drawLast || ent->bDrawLast){
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ ent->bDistanceFade = false;
+ return VIS_INVISIBLE;
+ }
+ return VIS_VISIBLE;
+ }
+
+ // Object is not loaded, figure out what to do
+
+ if(mi->m_noFade){
+ mi->m_isDamaged = false;
+ // request model
+ if(dist - STREAM_DISTANCE < mi->GetLargestLodDistance() && request)
+ return VIS_STREAMME;
+ return VIS_INVISIBLE;
+ }
+
+ // We might be fading
+
+ a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE);
+ mi->m_isDamaged = false;
+ if(a == nil){
+ // request model
+ if(dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance() && request)
+ return VIS_STREAMME;
+ return VIS_INVISIBLE;
+ }
+
+ if(ent->m_rwObject == nil)
+ ent->CreateRwObject();
+ assert(ent->m_rwObject);
+ RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject;
+ if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj))
+ RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0);
+ mi->IncreaseAlpha();
+ if(ent->m_rwObject == nil || !ent->bIsVisible)
+ return VIS_INVISIBLE;
+
+ if(!ent->GetIsOnScreen()){
+ mi->m_alpha = 255;
+ return VIS_OFFSCREEN;
+ }else{
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ ent->bDistanceFade = true;
+ return VIS_OFFSCREEN; // Why this?
+ }
+}
+
+int32
+CRenderer::SetupBigBuildingVisibility(CEntity *ent)
+{
+ CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex);
+ CTimeModelInfo *ti;
+ int32 other;
+
+ if(mi->m_type == MITYPE_TIME){
+ ti = (CTimeModelInfo*)mi;
+ other = ti->GetOtherTimeModel();
+ // Hide objects not in time range if possible
+ if(CANTIMECULL)
+ if(!CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff()))
+ return 0;
+ // Draw like normal
+ }else if(mi->m_type == MITYPE_VEHICLE)
+ return ent->IsVisible();
+
+ float dist = (ms_vecCameraPosition-ent->GetPosition()).Magnitude();
+ CSimpleModelInfo *nonLOD = mi->GetRelatedModel();
+
+ // Find out whether to draw below near distance.
+ // This is only the case if there is a non-LOD which is either not
+ // loaded or not completely faded in yet.
+ if(dist < mi->GetNearDistance() && dist < LOD_DISTANCE + STREAM_DISTANCE){
+ // No non-LOD or non-LOD is completely visible.
+ if(nonLOD == nil ||
+ nonLOD->GetRwObject() && nonLOD->m_alpha == 255)
+ return 0;
+
+ // But if it is a time object, we'd rather draw the wrong
+ // non-LOD than the right LOD.
+ if(nonLOD->m_type == MITYPE_TIME){
+ ti = (CTimeModelInfo*)nonLOD;
+ other = ti->GetOtherTimeModel();
+ if(other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject())
+ return 0;
+ }
+ }
+
+ RpAtomic *a = mi->GetAtomicFromDistance(dist);
+ if(a){
+ if(ent->m_rwObject == nil)
+ ent->CreateRwObject();
+ assert(ent->m_rwObject);
+ RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject;
+
+ // Make sure our atomic uses the right geometry and not
+ // that of an atomic for another draw distance.
+ if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj))
+ RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0);
+ if(!ent->IsVisibleComplex())
+ return 0;
+ if(mi->m_drawLast){
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ ent->bDistanceFade = 0;
+ return 0;
+ }
+ return 1;
+ }
+
+ if(mi->m_noFade){
+ ent->DeleteRwObject();
+ return 0;
+ }
+
+
+ // get faded atomic
+ a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE);
+ if(a == nil){
+ ent->DeleteRwObject();
+ return 0;
+ }
+
+ // Fade...
+ if(ent->m_rwObject == nil)
+ ent->CreateRwObject();
+ assert(ent->m_rwObject);
+ RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject;
+ if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj))
+ RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), 0);
+ if(ent->IsVisibleComplex())
+ CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist);
+ return 0;
+}
+
+void
+CRenderer::ConstructRenderList(void)
+{
+ ms_nNoOfVisibleEntities = 0;
+ ms_nNoOfInVisibleEntities = 0;
+ ms_vecCameraPosition = TheCamera.GetPosition();
+ // TODO: blocked ranges, but unused
+ ScanWorld();
+}
+
+void
+LimitFrustumVector(CVector &vec1, const CVector &vec2, float l)
+{
+ float f;
+ f = (l - vec2.z) / (vec1.z - vec2.z);
+ vec1.x = f*(vec1.x - vec2.x) + vec2.x;
+ vec1.y = f*(vec1.y - vec2.y) + vec2.y;
+ vec1.z = f*(vec1.z - vec2.z) + vec2.z;
+}
+
+enum Corners
+{
+ CORNER_CAM = 0,
+ CORNER_FAR_TOPLEFT,
+ CORNER_FAR_TOPRIGHT,
+ CORNER_FAR_BOTRIGHT,
+ CORNER_FAR_BOTLEFT,
+ CORNER_LOD_LEFT,
+ CORNER_LOD_RIGHT,
+ CORNER_PRIO_LEFT,
+ CORNER_PRIO_RIGHT,
+};
+
+void
+CRenderer::ScanWorld(void)
+{
+ float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera);
+ RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera);
+ CVector vectors[9];
+ RwMatrix *cammatrix;
+ RwV2d poly[3];
+
+ memset(vectors, 0, sizeof(vectors));
+ vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f;
+ vectors[CORNER_FAR_TOPLEFT].y = vw.y * f;
+ vectors[CORNER_FAR_TOPLEFT].z = f;
+ vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f;
+ vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f;
+ vectors[CORNER_FAR_TOPRIGHT].z = f;
+ vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f;
+ vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f;
+ vectors[CORNER_FAR_BOTRIGHT].z = f;
+ vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f;
+ vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f;
+ vectors[CORNER_FAR_BOTLEFT].z = f;
+
+ cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera));
+
+ m_pFirstPersonVehicle = nil;
+ CVisibilityPlugins::InitAlphaEntityList();
+ CWorld::AdvanceCurrentScanCode();
+
+ if(cammatrix->at.z > 0.0f){
+ // looking up, bottom corners are further away
+ vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f;
+ vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f;
+ }else{
+ // looking down, top corners are further away
+ vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f;
+ vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f;
+ }
+ vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f;
+ vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f;
+ vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z;
+ vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f;
+ vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f;
+ vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z;
+ RwV3dTransformPoints((RwV3d*)vectors, (RwV3d*)vectors, 9, cammatrix);
+
+ m_loadingPriority = false;
+ if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 ||
+ TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED){
+ CRect rect;
+ int x1, x2, y1, y2;
+ LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]);
+ LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]);
+ LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]);
+ LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]);
+ x1 = CWorld::GetSectorIndexX(rect.left);
+ if(x1 < 0) x1 = 0;
+ x2 = CWorld::GetSectorIndexX(rect.right);
+ if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1;
+ y1 = CWorld::GetSectorIndexY(rect.bottom);
+ if(y1 < 0) y1 = 0;
+ y2 = CWorld::GetSectorIndexY(rect.top);
+ if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1;
+ for(; x1 <= x2; x1++)
+ for(int y = y1; y <= y2; y++)
+ ScanSectorList(CWorld::GetSector(x1, y)->m_lists);
+ }else{
+ CVehicle *train = FindPlayerTrain();
+ if(train && train->GetPosition().z < 0.0f){
+ poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x);
+ poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y);
+ poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x);
+ poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y);
+ poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x);
+ poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y);
+ ScanSectorPoly(poly, 3, ScanSectorList_Subway);
+ }else{
+ if(f <= LOD_DISTANCE){
+ poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x);
+ poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y);
+ poly[1].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPLEFT].x);
+ poly[1].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPLEFT].y);
+ poly[2].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPRIGHT].x);
+ poly[2].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPRIGHT].y);
+ }else{
+ // priority
+ poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x);
+ poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y);
+ poly[1].x = CWorld::GetSectorX(vectors[CORNER_PRIO_LEFT].x);
+ poly[1].y = CWorld::GetSectorY(vectors[CORNER_PRIO_LEFT].y);
+ poly[2].x = CWorld::GetSectorX(vectors[CORNER_PRIO_RIGHT].x);
+ poly[2].y = CWorld::GetSectorY(vectors[CORNER_PRIO_RIGHT].y);
+ ScanSectorPoly(poly, 3, ScanSectorList_Priority);
+
+ // below LOD
+ poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x);
+ poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y);
+ poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x);
+ poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y);
+ poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x);
+ poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y);
+ }
+ ScanSectorPoly(poly, 3, ScanSectorList);
+
+ ScanBigBuildingList(CWorld::GetBigBuildingList(CCollision::ms_collisionInMemory));
+ ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_NONE));
+ }
+ }
+}
+
+void
+CRenderer::RequestObjectsInFrustum(void)
+{
+ float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera);
+ RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera);
+ CVector vectors[9];
+ RwMatrix *cammatrix;
+ RwV2d poly[3];
+
+ memset(vectors, 0, sizeof(vectors));
+ vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f;
+ vectors[CORNER_FAR_TOPLEFT].y = vw.y * f;
+ vectors[CORNER_FAR_TOPLEFT].z = f;
+ vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f;
+ vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f;
+ vectors[CORNER_FAR_TOPRIGHT].z = f;
+ vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f;
+ vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f;
+ vectors[CORNER_FAR_BOTRIGHT].z = f;
+ vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f;
+ vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f;
+ vectors[CORNER_FAR_BOTLEFT].z = f;
+
+ cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera));
+
+ CWorld::AdvanceCurrentScanCode();
+
+ if(cammatrix->at.z > 0.0f){
+ // looking up, bottom corners are further away
+ vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f;
+ vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f;
+ }else{
+ // looking down, top corners are further away
+ vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f;
+ vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f;
+ }
+ vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f;
+ vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f;
+ vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z;
+ vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f;
+ vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f;
+ vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z;
+ RwV3dTransformPoints((RwV3d*)vectors, (RwV3d*)vectors, 9, cammatrix);
+
+ if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 ||
+ TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED){
+ CRect rect;
+ int x1, x2, y1, y2;
+ LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]);
+ LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]);
+ LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]);
+ LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f);
+ rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]);
+ x1 = CWorld::GetSectorIndexX(rect.left);
+ if(x1 < 0) x1 = 0;
+ x2 = CWorld::GetSectorIndexX(rect.right);
+ if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1;
+ y1 = CWorld::GetSectorIndexY(rect.bottom);
+ if(y1 < 0) y1 = 0;
+ y2 = CWorld::GetSectorIndexY(rect.top);
+ if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1;
+ for(; x1 <= x2; x1++)
+ for(int y = y1; y <= y2; y++)
+ ScanSectorList_RequestModels(CWorld::GetSector(x1, y)->m_lists);
+ }else{
+ poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x);
+ poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y);
+ poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x);
+ poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y);
+ poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x);
+ poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y);
+ ScanSectorPoly(poly, 3, ScanSectorList_RequestModels);
+ }
+}
+
+float
+CalcNewDelta(RwV2d *a, RwV2d *b)
+{
+ return (b->x - a->x) / (b->y - a->y);
+}
+
+void
+CRenderer::ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *))
+{
+ float miny, maxy;
+ int y, yend;
+ int x, xstart, xend;
+ int i;
+ int a1, a2, b1, b2;
+ float deltaA, deltaB;
+ float xA, xB;
+
+ miny = poly[0].y;
+ maxy = poly[0].y;
+ a2 = 0;
+ xstart = 9999;
+ xend = -9999;
+
+ for(i = 1; i < numVertices; i++){
+ if(poly[i].y > maxy)
+ maxy = poly[i].y;
+ if(poly[i].y < miny){
+ miny = poly[i].y;
+ a2 = i;
+ }
+ }
+ y = miny;
+ yend = maxy;
+
+ // Go left in poly to find first edge b
+ b2 = a2;
+ for(i = 0; i < numVertices; i++){
+ b1 = b2--;
+ if(b2 < 0) b2 = numVertices-1;
+ if(poly[b1].x < xstart)
+ xstart = poly[b1].x;
+ if((int)poly[b1].y != (int)poly[b2].y)
+ break;
+ }
+ // Go right to find first edge a
+ for(i = 0; i < numVertices; i++){
+ a1 = a2++;
+ if(a2 == numVertices) a2 = 0;
+ if(poly[a1].x > xend)
+ xend = poly[a1].x;
+ if((int)poly[a1].y != (int)poly[a2].y)
+ break;
+ }
+
+ // prestep x1 and x2 to next integer y
+ deltaA = CalcNewDelta(&poly[a1], &poly[a2]);
+ xA = deltaA * (ceilf(poly[a1].y) - poly[a1].y) + poly[a1].x;
+ deltaB = CalcNewDelta(&poly[b1], &poly[b2]);
+ xB = deltaB * (ceilf(poly[b1].y) - poly[b1].y) + poly[b1].x;
+
+ if(y != yend){
+ if(deltaB < 0.0f && (int)xB < xstart)
+ xstart = xB;
+ if(deltaA >= 0.0f && (int)xA > xend)
+ xend = xA;
+ }
+
+ while(y <= yend && y < NUMSECTORS_Y){
+ // scan one x-line
+ if(y >= 0 && xstart < NUMSECTORS_X)
+ for(x = xstart; x <= xend; x++)
+ if(x >= 0 && x != NUMSECTORS_X)
+ scanfunc(CWorld::GetSector(x, y)->m_lists);
+
+ // advance one scan line
+ y++;
+ xA += deltaA;
+ xB += deltaB;
+
+ // update left side
+ if(y == (int)poly[b2].y){
+ // reached end of edge
+ if(y == yend){
+ if(deltaB < 0.0f){
+ do{
+ xstart = poly[b2--].x;
+ if(b2 < 0) b2 = numVertices-1;
+ }while(xstart > (int)poly[b2].x);
+ }else
+ xstart = xB - deltaB;
+ }else{
+ // switch edges
+ if(deltaB < 0.0f)
+ xstart = poly[b2].x;
+ else
+ xstart = xB - deltaB;
+ do{
+ b1 = b2--;
+ if(b2 < 0) b2 = numVertices-1;
+ if((int)poly[b1].x < xstart)
+ xstart = poly[b1].x;
+ }while(y == (int)poly[b2].y);
+ deltaB = CalcNewDelta(&poly[b1], &poly[b2]);
+ xB = deltaB * (ceilf(poly[b1].y) - poly[b1].y) + poly[b1].x;
+ if(deltaB < 0.0f && (int)xB < xstart)
+ xstart = xB;
+ }
+ }else{
+ if(deltaB < 0.0f)
+ xstart = xB;
+ else
+ xstart = xB - deltaB;
+ }
+
+ // update right side
+ if(y == (int)poly[a2].y){
+ // reached end of edge
+ if(y == yend){
+ if(deltaA < 0.0f)
+ xend = xA - deltaA;
+ else{
+ do{
+ xend = poly[a2++].x;
+ if(a2 == numVertices) a2 = 0;
+ }while(xend < (int)poly[a2].x);
+ }
+ }else{
+ // switch edges
+ if(deltaA < 0.0f)
+ xend = xA - deltaA;
+ else
+ xend = poly[a2].x;
+ do{
+ a1 = a2++;
+ if(a2 == numVertices) a2 = 0;
+ if((int)poly[a1].x > xend)
+ xend = poly[a1].x;
+ }while(y == (int)poly[a2].y);
+ deltaA = CalcNewDelta(&poly[a1], &poly[a2]);
+ xA = deltaA * (ceilf(poly[a1].y) - poly[a1].y) + poly[a1].x;
+ if(deltaA >= 0.0f && (int)xA > xend)
+ xend = xA;
+ }
+ }else{
+ if(deltaA < 0.0f)
+ xend = xA - deltaA;
+ else
+ xend = xA;
+ }
+ }
+}
+
+void
+CRenderer::ScanBigBuildingList(CPtrList &list)
+{
+ CPtrNode *node;
+ CEntity *ent;
+
+ for(node = list.first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(!ent->m_bZoneCulled && SetupBigBuildingVisibility(ent) == 1)
+ ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent;
+ }
+}
+
+void
+CRenderer::ScanSectorList(CPtrList *lists)
+{
+ CPtrNode *node;
+ CPtrList *list;
+ CEntity *ent;
+ int i;
+ float dx, dy;
+
+ for(i = 0; i < NUMSECTORENTITYLISTS; i++){
+ list = &lists[i];
+ for(node = list->first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(ent->m_scanCode == CWorld::GetCurrentScanCode())
+ continue; // already seen
+ ent->m_scanCode = CWorld::GetCurrentScanCode();
+
+ if(IsEntityCullZoneVisible(ent))
+ switch(SetupEntityVisibility(ent)){
+ case VIS_VISIBLE:
+ ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent;
+ break;
+ case VIS_INVISIBLE:
+ if(!IsGlass(ent->GetModelIndex()))
+ break;
+ // fall through
+ case VIS_OFFSCREEN:
+ dx = ms_vecCameraPosition.x - ent->GetPosition().x;
+ dy = ms_vecCameraPosition.y - ent->GetPosition().y;
+ if(dx > -65.0f && dx < 65.0f &&
+ dy > -65.0f && dy < 65.0f &&
+ ms_nNoOfInVisibleEntities < 150)
+ ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent;
+ break;
+ case VIS_STREAMME:
+ if(!CStreaming::ms_disableStreaming)
+ if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10)
+ CStreaming::RequestModel(ent->GetModelIndex(), 0);
+ break;
+ }
+ else if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){
+ if(!CStreaming::ms_disableStreaming)
+ if(SetupEntityVisibility(ent) == VIS_STREAMME)
+ if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10)
+ CStreaming::RequestModel(ent->GetModelIndex(), 0);
+ }
+ }
+ }
+}
+
+void
+CRenderer::ScanSectorList_Priority(CPtrList *lists)
+{
+ CPtrNode *node;
+ CPtrList *list;
+ CEntity *ent;
+ int i;
+ float dx, dy;
+
+ for(i = 0; i < NUMSECTORENTITYLISTS; i++){
+ list = &lists[i];
+ for(node = list->first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(ent->m_scanCode == CWorld::GetCurrentScanCode())
+ continue; // already seen
+ ent->m_scanCode = CWorld::GetCurrentScanCode();
+
+ if(IsEntityCullZoneVisible(ent))
+ switch(SetupEntityVisibility(ent)){
+ case VIS_VISIBLE:
+ ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent;
+ break;
+ case VIS_INVISIBLE:
+ if(!IsGlass(ent->GetModelIndex()))
+ break;
+ // fall through
+ case VIS_OFFSCREEN:
+ dx = ms_vecCameraPosition.x - ent->GetPosition().x;
+ dy = ms_vecCameraPosition.y - ent->GetPosition().y;
+ if(dx > -65.0f && dx < 65.0f &&
+ dy > -65.0f && dy < 65.0f &&
+ ms_nNoOfInVisibleEntities < 150)
+ ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent;
+ break;
+ case VIS_STREAMME:
+ if(!CStreaming::ms_disableStreaming){
+ CStreaming::RequestModel(ent->GetModelIndex(), 0);
+ if(CStreaming::ms_aInfoForModel[ent->GetModelIndex()].m_loadState != STREAM_LOADED)
+ m_loadingPriority = true;
+ }
+ break;
+ }
+ else if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){
+ if(!CStreaming::ms_disableStreaming)
+ if(SetupEntityVisibility(ent) == VIS_STREAMME)
+ CStreaming::RequestModel(ent->GetModelIndex(), 0);
+ }
+ }
+ }
+}
+
+void
+CRenderer::ScanSectorList_Subway(CPtrList *lists)
+{
+ CPtrNode *node;
+ CPtrList *list;
+ CEntity *ent;
+ int i;
+ float dx, dy;
+
+ for(i = 0; i < NUMSECTORENTITYLISTS; i++){
+ list = &lists[i];
+ for(node = list->first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(ent->m_scanCode == CWorld::GetCurrentScanCode())
+ continue; // already seen
+ ent->m_scanCode = CWorld::GetCurrentScanCode();
+ switch(SetupEntityVisibility(ent)){
+ case VIS_VISIBLE:
+ ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent;
+ break;
+ case VIS_OFFSCREEN:
+ dx = ms_vecCameraPosition.x - ent->GetPosition().x;
+ dy = ms_vecCameraPosition.y - ent->GetPosition().y;
+ if(dx > -65.0f && dx < 65.0f &&
+ dy > -65.0f && dy < 65.0f &&
+ ms_nNoOfInVisibleEntities < 150)
+ ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent;
+ break;
+ }
+ }
+ }
+}
+
+void
+CRenderer::ScanSectorList_RequestModels(CPtrList *lists)
+{
+ CPtrNode *node;
+ CPtrList *list;
+ CEntity *ent;
+ int i;
+
+ for(i = 0; i < NUMSECTORENTITYLISTS; i++){
+ list = &lists[i];
+ for(node = list->first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(ent->m_scanCode == CWorld::GetCurrentScanCode())
+ continue; // already seen
+ ent->m_scanCode = CWorld::GetCurrentScanCode();
+ if(IsEntityCullZoneVisible(ent) && ShouldModelBeStreamed(ent))
+ CStreaming::RequestModel(ent->GetModelIndex(), 0);
+ }
+ }
+}
+
+// Put big buildings in front
+// This seems pointless because the sector lists shouldn't have big buildings in the first place
+void
+CRenderer::SortBIGBuildings(void)
+{
+ int x, y;
+ for(y = 0; y < NUMSECTORS_Y; y++)
+ for(x = 0; x < NUMSECTORS_X; x++){
+ SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS]);
+ SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]);
+ }
+}
+
+void
+CRenderer::SortBIGBuildingsForSectorList(CPtrList *list)
+{
+ CPtrNode *node;
+ CEntity *ent;
+
+ for(node = list->first; node; node = node->next){
+ ent = (CEntity*)node->item;
+ if(ent->bIsBIGBuilding){
+ list->RemoveNode(node);
+ list->InsertNode(node);
+ }
+ }
+}
+
+bool
+CRenderer::ShouldModelBeStreamed(CEntity *ent)
+{
+ CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex);
+ float dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude();
+ if(mi->m_noFade)
+ return dist - STREAM_DISTANCE < mi->GetLargestLodDistance();
+ else
+ return dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance();
+}
+
+bool
+CRenderer::IsEntityCullZoneVisible(CEntity *ent)
+{
+ CPed *ped;
+ CObject *obj;
+
+ if(ent->m_bZoneCulled)
+ return false;
+
+ switch(ent->m_type){
+ case ENTITY_TYPE_VEHICLE:
+ return IsVehicleCullZoneVisible(ent);
+ case ENTITY_TYPE_PED:
+ ped = (CPed*)ent;
+ if(ped->bInVehicle)
+ return ped->m_pMyVehicle && IsVehicleCullZoneVisible(ped->m_pMyVehicle);
+ return !(ped->m_pCurSurface && ped->m_pCurSurface->m_bZoneCulled2);
+ case ENTITY_TYPE_OBJECT:
+ obj = (CObject*)ent;
+ if(!obj->bIsStatic)
+ return true;
+ return !(obj->m_pCurSurface && obj->m_pCurSurface->m_bZoneCulled2);
+ }
+ return true;
+}
+
+bool
+CRenderer::IsVehicleCullZoneVisible(CEntity *ent)
+{
+ CVehicle *v = (CVehicle*)ent;
+ switch(v->m_status)
+ case STATUS_SIMPLE:
+ case STATUS_PHYSICS:
+ case STATUS_ABANDONED:
+ case STATUS_WRECKED:
+ return !(v->m_pCurSurface && v->m_pCurSurface->m_bZoneCulled2);
+ return true;
+}
+
+STARTPATCHES
+ InjectHook(0x4A7680, CRenderer::Init, PATCH_JUMP);
+
+ InjectHook(0x4A7B90, CRenderer::RenderOneRoad, PATCH_JUMP);
+ InjectHook(0x4A7BA0, CRenderer::RenderOneNonRoad, PATCH_JUMP);
+ InjectHook(0x4A7B20, CRenderer::RenderFirstPersonVehicle, PATCH_JUMP);
+ InjectHook(0x4A78B0, CRenderer::RenderRoads, PATCH_JUMP);
+ InjectHook(0x4A7930, CRenderer::RenderEverythingBarRoads, PATCH_JUMP);
+ InjectHook(0x4A7AA0, CRenderer::RenderVehiclesButNotBoats, PATCH_JUMP);
+ InjectHook(0x4A7AE0, CRenderer::RenderBoats, PATCH_JUMP);
+ InjectHook(0x4A7910, CRenderer::RenderFadingInEntities, PATCH_JUMP);
+
+ InjectHook(0x4A9350, CRenderer::SetupEntityVisibility, PATCH_JUMP);
+ InjectHook(0x4A9920, CRenderer::SetupBigBuildingVisibility, PATCH_JUMP);
+
+ InjectHook(0x4A76B0, CRenderer::ConstructRenderList, PATCH_JUMP);
+ InjectHook(0x4A8970, CRenderer::ScanWorld, PATCH_JUMP);
+ InjectHook(0x4AA240, CRenderer::RequestObjectsInFrustum, PATCH_JUMP);
+ InjectHook(0x4A7F30, CRenderer::ScanSectorPoly, PATCH_JUMP);
+ InjectHook(0x4A9300, CRenderer::ScanBigBuildingList, PATCH_JUMP);
+ InjectHook(0x4A9BB0, CRenderer::ScanSectorList, PATCH_JUMP);
+ InjectHook(0x4A9E30, CRenderer::ScanSectorList_Priority, PATCH_JUMP);
+ InjectHook(0x4AA0A0, CRenderer::ScanSectorList_Subway, PATCH_JUMP);
+ InjectHook(0x4AA1D0, CRenderer::ScanSectorList_RequestModels, PATCH_JUMP);
+
+ InjectHook(0x4AA940, CRenderer::SortBIGBuildings, PATCH_JUMP);
+ InjectHook(0x4AA990, CRenderer::SortBIGBuildingsForSectorList, PATCH_JUMP);
+
+ InjectHook(0x4A9840, CRenderer::ShouldModelBeStreamed, PATCH_JUMP);
+ InjectHook(0x4AAA00, CRenderer::IsEntityCullZoneVisible, PATCH_JUMP);
+ InjectHook(0x4AAAA0, CRenderer::IsVehicleCullZoneVisible, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/Renderer.h b/src/render/Renderer.h
new file mode 100644
index 00000000..970d6ba5
--- /dev/null
+++ b/src/render/Renderer.h
@@ -0,0 +1,59 @@
+#pragma once
+
+class CEntity;
+
+extern bool gbShowPedRoadGroups;
+extern bool gbShowCarRoadGroups;
+extern bool gbShowCollisionPolys;
+
+extern bool gbDontRenderBuildings;
+extern bool gbDontRenderBigBuildings;
+extern bool gbDontRenderPeds;
+extern bool gbDontRenderObjects;
+
+class CVehicle;
+class CPtrList;
+
+class CRenderer
+{
+ static int32 &ms_nNoOfVisibleEntities;
+ static CEntity **ms_aVisibleEntityPtrs; // [2000];
+ static int32 &ms_nNoOfInVisibleEntities;
+ static CEntity **ms_aInVisibleEntityPtrs; // [150];
+
+ static CVector &ms_vecCameraPosition;
+ static CVehicle *&m_pFirstPersonVehicle;
+ static bool &m_loadingPriority;
+public:
+ static void Init(void);
+ // TODO: PreRender, needs CHeli and CShadows
+
+ static void RenderRoads(void);
+ static void RenderFadingInEntities(void);
+ static void RenderEverythingBarRoads(void);
+ static void RenderVehiclesButNotBoats(void);
+ static void RenderBoats(void);
+ static void RenderOneRoad(CEntity *);
+ static void RenderOneNonRoad(CEntity *);
+ static void RenderFirstPersonVehicle(void);
+
+ static int32 SetupEntityVisibility(CEntity *ent);
+ static int32 SetupBigBuildingVisibility(CEntity *ent);
+
+ static void ConstructRenderList(void);
+ static void ScanWorld(void);
+ static void RequestObjectsInFrustum(void);
+ static void ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *));
+ static void ScanBigBuildingList(CPtrList &list);
+ static void ScanSectorList(CPtrList *lists);
+ static void ScanSectorList_Priority(CPtrList *lists);
+ static void ScanSectorList_Subway(CPtrList *lists);
+ static void ScanSectorList_RequestModels(CPtrList *lists);
+
+ static void SortBIGBuildings(void);
+ static void SortBIGBuildingsForSectorList(CPtrList *list);
+
+ static bool ShouldModelBeStreamed(CEntity *ent);
+ static bool IsEntityCullZoneVisible(CEntity *ent);
+ static bool IsVehicleCullZoneVisible(CEntity *ent);
+};
diff --git a/src/render/Sprite.cpp b/src/render/Sprite.cpp
new file mode 100644
index 00000000..74eefccf
--- /dev/null
+++ b/src/render/Sprite.cpp
@@ -0,0 +1,553 @@
+#include "common.h"
+#include "patcher.h"
+#include "Draw.h"
+#include "Camera.h"
+#include "Sprite.h"
+
+// Get rid of bullshit windows definitions, we're not running on an 8086
+#ifdef far
+#undef far
+#undef near
+#endif
+
+RwIm2DVertex *CSprite2d::maVertices = (RwIm2DVertex*)0x6E9168;
+float &CSprite2d::RecipNearClip = *(float*)0x880DB4;
+
+// Arguments:
+// 2---3
+// | |
+// 0---1
+void
+CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far)
+{
+ float screenz, z, recipz;
+
+ if(far){
+ screenz = RwIm2DGetFarScreenZ();
+ z = RwCameraGetFarClipPlane(Scene.camera);
+ }else{
+ screenz = RwIm2DGetNearScreenZ();
+ z = 1.0f/RecipNearClip;
+ }
+ recipz = 1.0f/z;
+
+ // This is what we draw:
+ // 0---1
+ // | / |
+ // 3---2
+ RwIm2DVertexSetScreenX(&maVertices[0], r.left);
+ RwIm2DVertexSetScreenY(&maVertices[0], r.bottom);
+ RwIm2DVertexSetScreenZ(&maVertices[0], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[0], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a);
+ RwIm2DVertexSetU(&maVertices[0], 0.0f, recipz);
+ RwIm2DVertexSetV(&maVertices[0], 0.0f, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[1], r.right);
+ RwIm2DVertexSetScreenY(&maVertices[1], r.bottom);
+ RwIm2DVertexSetScreenZ(&maVertices[1], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[1], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a);
+ RwIm2DVertexSetU(&maVertices[1], 1.0f, recipz);
+ RwIm2DVertexSetV(&maVertices[1], 0.0f, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[2], r.right);
+ RwIm2DVertexSetScreenY(&maVertices[2], r.top);
+ RwIm2DVertexSetScreenZ(&maVertices[2], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[2], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a);
+ RwIm2DVertexSetU(&maVertices[2], 1.0f, recipz);
+ RwIm2DVertexSetV(&maVertices[2], 1.0f, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[3], r.left);
+ RwIm2DVertexSetScreenY(&maVertices[3], r.top);
+ RwIm2DVertexSetScreenZ(&maVertices[3], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[3], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a);
+ RwIm2DVertexSetU(&maVertices[3], 0.0f, recipz);
+ RwIm2DVertexSetV(&maVertices[3], 1.0f, recipz);
+}
+
+void
+CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3,
+ float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2)
+{
+ float screenz, z, recipz;
+
+ screenz = RwIm2DGetNearScreenZ();
+ z = 1.0f/RecipNearClip;
+ recipz = 1.0f/z;
+
+ // This is what we draw:
+ // 0---1
+ // | / |
+ // 3---2
+ RwIm2DVertexSetScreenX(&maVertices[0], r.left);
+ RwIm2DVertexSetScreenY(&maVertices[0], r.bottom);
+ RwIm2DVertexSetScreenZ(&maVertices[0], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[0], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a);
+ RwIm2DVertexSetU(&maVertices[0], u0, recipz);
+ RwIm2DVertexSetV(&maVertices[0], v0, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[1], r.right);
+ RwIm2DVertexSetScreenY(&maVertices[1], r.bottom);
+ RwIm2DVertexSetScreenZ(&maVertices[1], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[1], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a);
+ RwIm2DVertexSetU(&maVertices[1], u1, recipz);
+ RwIm2DVertexSetV(&maVertices[1], v1, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[2], r.right);
+ RwIm2DVertexSetScreenY(&maVertices[2], r.top);
+ RwIm2DVertexSetScreenZ(&maVertices[2], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[2], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a);
+ RwIm2DVertexSetU(&maVertices[2], u2, recipz);
+ RwIm2DVertexSetV(&maVertices[2], v2, recipz);
+
+ RwIm2DVertexSetScreenX(&maVertices[3], r.left);
+ RwIm2DVertexSetScreenY(&maVertices[3], r.top);
+ RwIm2DVertexSetScreenZ(&maVertices[3], screenz);
+ RwIm2DVertexSetCameraZ(&maVertices[3], z);
+ RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz);
+ RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a);
+ RwIm2DVertexSetU(&maVertices[3], u3, recipz);
+ RwIm2DVertexSetV(&maVertices[3], v3, recipz);
+}
+
+void
+CSprite2d::SetRenderState(void)
+{
+ if(m_pTexture)
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(m_pTexture));
+ else
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+}
+
+void
+CSprite2d::DrawRect(const CRect &r, const CRGBA &col)
+{
+ SetVertices(r, col, col, col, col, false);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(col.a != 255));
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD);
+}
+
+void
+CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3)
+{
+ SetVertices(r, c0, c1, c2, c3, false);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE);
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+}
+
+void
+CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3)
+{
+ SetVertices(r, c0, c1, c2, c3, false);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+}
+
+
+
+float &CSprite::m_f2DNearScreenZ = *(float*)0x8F1ABC;
+float &CSprite::m_f2DFarScreenZ = *(float*)0x8F2C94;
+int32 &CSprite::m_bFlushSpriteBufferSwitchZTest = *(int32*)0x8F5FB0;
+
+float
+CSprite::CalcHorizonCoors(void)
+{
+ CVector p = TheCamera.GetPosition() + CVector(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm, 0.0f)*3000.0f;
+ p.z = 0.0f;
+ p = TheCamera.m_viewMatrix * p;
+ return p.y * RsGlobal.maximumHeight / p.z;
+}
+
+bool
+CSprite::CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip)
+{
+ CVector viewvec = TheCamera.m_viewMatrix * *(CVector*)&in;
+ *out = *(RwV3d*)&viewvec;
+ if(out->z <= CDraw::GetNearClipZ() + 1.0f) return false;
+ if(out->z >= CDraw::GetFarClipZ() && farclip) return false;
+ float recip = 1.0f/out->z;
+ out->x *= RsGlobal.maximumWidth * recip;
+ out->y *= RsGlobal.maximumHeight * recip;
+ // What is this? size?
+ *outw = 70.0f/CDraw::GetFOV();
+ *outh = 70.0f/CDraw::GetFOV();
+ *outw *= RsGlobal.maximumWidth * recip;
+ *outh *= RsGlobal.maximumHeight * recip;
+ return true;
+}
+
+#define SPRITEBUFFERSIZE 64
+static int32 &nSpriteBufferIndex = *(int32*)0x649A80;
+static RwIm2DVertex *SpriteBufferVerts = (RwIm2DVertex*)0x649A84; //[SPRITEBUFFERSIZE*6];
+static RwIm2DVertex *verts = (RwIm2DVertex*)0x64C484; //[4];
+
+void
+CSprite::InitSpriteBuffer(void)
+{
+ m_f2DNearScreenZ = RwIm2DGetNearScreenZ();
+ m_f2DFarScreenZ = RwIm2DGetFarScreenZ();
+}
+
+void
+CSprite::FlushSpriteBuffer(void)
+{
+ if(nSpriteBufferIndex > 0){
+ if(m_bFlushSpriteBufferSwitchZTest){
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+ RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6);
+ RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE);
+ }else
+ RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6);
+ nSpriteBufferIndex = 0;
+ }
+}
+
+void
+CSprite::RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a)
+{
+ static short indices[] = { 0, 1, 2, 3 };
+ // 0---3
+ // | |
+ // 1---2
+ float xs[4];
+ float ys[4];
+ float us[4];
+ float vs[4];
+ int i;
+
+ xs[0] = x-w; us[0] = 0.0f;
+ xs[1] = x-w; us[1] = 0.0f;
+ xs[2] = x+w; us[2] = 1.0f;
+ xs[3] = x+w; us[3] = 1.0f;
+
+ ys[0] = y-h; vs[0] = 0.0f;
+ ys[1] = y+h; vs[1] = 1.0f;
+ ys[2] = y+h; vs[2] = 1.0f;
+ ys[3] = y-h; vs[3] = 0.0f;
+
+ // clip
+ for(i = 0; i < 4; i++){
+ if(xs[i] < 0.0f){
+ us[i] = -xs[i] / (2.0f*w);
+ xs[i] = 0.0f;
+ }
+ if(xs[i] > RsGlobal.maximumWidth){
+ us[i] = 1.0f - (xs[i]-RsGlobal.maximumWidth) / (2.0f*w);
+ xs[i] = RsGlobal.maximumWidth;
+ }
+ if(ys[i] < 0.0f){
+ vs[i] = -ys[i] / (2.0f*h);
+ ys[i] = 0.0f;
+ }
+ if(ys[i] > RsGlobal.maximumHeight){
+ vs[i] = 1.0f - (ys[i]-RsGlobal.maximumHeight) / (2.0f*h);
+ ys[i] = RsGlobal.maximumHeight;
+ }
+ }
+
+ // (DrawZ - DrawNear)/(DrawFar - DrawNear) = (SpriteZ-SpriteNear)/(SpriteFar-SpriteNear)
+ // So to calculate SpriteZ:
+ float screenz = m_f2DNearScreenZ +
+ (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() /
+ ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z);
+
+ for(i = 0; i < 4; i++){
+ RwIm2DVertexSetScreenX(&verts[i], xs[i]);
+ RwIm2DVertexSetScreenY(&verts[i], ys[i]);
+ RwIm2DVertexSetScreenZ(&verts[i], screenz);
+ RwIm2DVertexSetCameraZ(&verts[i], z);
+ RwIm2DVertexSetRecipCameraZ(&verts[i], recipz);
+ RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a);
+ RwIm2DVertexSetU(&verts[i], us[i], recipz);
+ RwIm2DVertexSetV(&verts[i], vs[i], recipz);
+ }
+ RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4);
+}
+
+void
+CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a)
+{
+ m_bFlushSpriteBufferSwitchZTest = 0;
+
+ // 0---3
+ // | |
+ // 1---2
+ float xs[4];
+ float ys[4];
+ float us[4];
+ float vs[4];
+ int i;
+
+ xs[0] = x-w; us[0] = 0.0f;
+ xs[1] = x-w; us[1] = 0.0f;
+ xs[2] = x+w; us[2] = 1.0f;
+ xs[3] = x+w; us[3] = 1.0f;
+
+ ys[0] = y-h; vs[0] = 0.0f;
+ ys[1] = y+h; vs[1] = 1.0f;
+ ys[2] = y+h; vs[2] = 1.0f;
+ ys[3] = y-h; vs[3] = 0.0f;
+
+ // clip
+ for(i = 0; i < 4; i++){
+ if(xs[i] < 0.0f){
+ us[i] = -xs[i] / (2.0f*w);
+ xs[i] = 0.0f;
+ }
+ if(xs[i] > RsGlobal.maximumWidth){
+ us[i] = 1.0f - (xs[i]-RsGlobal.maximumWidth) / (2.0f*w);
+ xs[i] = RsGlobal.maximumWidth;
+ }
+ if(ys[i] < 0.0f){
+ vs[i] = -ys[i] / (2.0f*h);
+ ys[i] = 0.0f;
+ }
+ if(ys[i] > RsGlobal.maximumHeight){
+ vs[i] = 1.0f - (ys[i]-RsGlobal.maximumHeight) / (2.0f*h);
+ ys[i] = RsGlobal.maximumHeight;
+ }
+ }
+
+ float screenz = m_f2DNearScreenZ +
+ (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() /
+ ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z);
+
+ RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6];
+ static int indices[6] = { 0, 1, 2, 3, 0, 2 };
+ for(i = 0; i < 6; i++){
+ RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]);
+ RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]);
+ RwIm2DVertexSetScreenZ(&vert[i], screenz);
+ RwIm2DVertexSetCameraZ(&vert[i], z);
+ RwIm2DVertexSetRecipCameraZ(&vert[i], recipz);
+ RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a);
+ RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz);
+ RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz);
+ }
+ nSpriteBufferIndex++;
+ if(nSpriteBufferIndex >= SPRITEBUFFERSIZE)
+ FlushSpriteBuffer();
+}
+
+void
+CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a)
+{
+ m_bFlushSpriteBufferSwitchZTest = 0;
+ // TODO: replace with lookup
+ float c = cos(DEGTORAD(rotation));
+ float s = sin(DEGTORAD(rotation));
+
+ float xs[4];
+ float ys[4];
+ float us[4];
+ float vs[4];
+ int i;
+
+ xs[0] = x - c*w - s*h; us[0] = 0.0f;
+ xs[1] = x - c*w + s*h; us[1] = 0.0f;
+ xs[2] = x + c*w + s*h; us[2] = 1.0f;
+ xs[3] = x + c*w - s*h; us[3] = 1.0f;
+
+ ys[0] = y - c*h + s*w; vs[0] = 0.0f;
+ ys[1] = y + c*h + s*w; vs[1] = 1.0f;
+ ys[2] = y + c*h - s*w; vs[2] = 1.0f;
+ ys[3] = y - c*h - s*w; vs[3] = 0.0f;
+
+ // No clipping, just culling
+ if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return;
+ if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return;
+ if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth &&
+ xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return;
+ if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight &&
+ ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return;
+
+ float screenz = m_f2DNearScreenZ +
+ (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() /
+ ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z);
+
+ RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6];
+ static int indices[6] = { 0, 1, 2, 3, 0, 2 };
+ for(i = 0; i < 6; i++){
+ RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]);
+ RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]);
+ RwIm2DVertexSetScreenZ(&vert[i], screenz);
+ RwIm2DVertexSetCameraZ(&vert[i], z);
+ RwIm2DVertexSetRecipCameraZ(&vert[i], recipz);
+ RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a);
+ RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz);
+ RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz);
+ }
+ nSpriteBufferIndex++;
+ if(nSpriteBufferIndex >= SPRITEBUFFERSIZE)
+ FlushSpriteBuffer();
+}
+
+void
+CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a)
+{
+ m_bFlushSpriteBufferSwitchZTest = 0;
+ float c = cos(DEGTORAD(rotation));
+ float s = sin(DEGTORAD(rotation));
+
+ float xs[4];
+ float ys[4];
+ float us[4];
+ float vs[4];
+ int i;
+
+ xs[0] = x + w*(-c-s); us[0] = 0.0f;
+ xs[1] = x + w*(-c+s); us[1] = 0.0f;
+ xs[2] = x + w*(+c+s); us[2] = 1.0f;
+ xs[3] = x + w*(+c-s); us[3] = 1.0f;
+
+ ys[0] = y + h*(-c+s); vs[0] = 0.0f;
+ ys[1] = y + h*(+c+s); vs[1] = 1.0f;
+ ys[2] = y + h*(+c-s); vs[2] = 1.0f;
+ ys[3] = y + h*(-c-s); vs[3] = 0.0f;
+
+ // No clipping, just culling
+ if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return;
+ if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return;
+ if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth &&
+ xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return;
+ if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight &&
+ ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return;
+
+ float screenz = m_f2DNearScreenZ +
+ (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() /
+ ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z);
+
+ RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6];
+ static int indices[6] = { 0, 1, 2, 3, 0, 2 };
+ for(i = 0; i < 6; i++){
+ RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]);
+ RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]);
+ RwIm2DVertexSetScreenZ(&vert[i], screenz);
+ RwIm2DVertexSetCameraZ(&vert[i], z);
+ RwIm2DVertexSetRecipCameraZ(&vert[i], recipz);
+ RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a);
+ RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz);
+ RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz);
+ }
+ nSpriteBufferIndex++;
+ if(nSpriteBufferIndex >= SPRITEBUFFERSIZE)
+ FlushSpriteBuffer();
+}
+
+void
+CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a)
+{
+ m_bFlushSpriteBufferSwitchZTest = 0;
+ float c = cos(DEGTORAD(rotation));
+ float s = sin(DEGTORAD(rotation));
+
+ float xs[4];
+ float ys[4];
+ float us[4];
+ float vs[4];
+ float cf[4];
+ int i;
+
+ xs[0] = x + w*(-c-s); us[0] = 0.0f;
+ xs[1] = x + w*(-c+s); us[1] = 0.0f;
+ xs[2] = x + w*(+c+s); us[2] = 1.0f;
+ xs[3] = x + w*(+c-s); us[3] = 1.0f;
+
+ ys[0] = y + h*(-c+s); vs[0] = 0.0f;
+ ys[1] = y + h*(+c+s); vs[1] = 1.0f;
+ ys[2] = y + h*(+c-s); vs[2] = 1.0f;
+ ys[3] = y + h*(-c-s); vs[3] = 0.0f;
+
+ // No clipping, just culling
+ if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return;
+ if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return;
+ if(xs[0] > RsGlobal.maximumWidth && xs[1] > RsGlobal.maximumWidth &&
+ xs[2] > RsGlobal.maximumWidth && xs[3] > RsGlobal.maximumWidth) return;
+ if(ys[0] > RsGlobal.maximumHeight && ys[1] > RsGlobal.maximumHeight &&
+ ys[2] > RsGlobal.maximumHeight && ys[3] > RsGlobal.maximumHeight) return;
+
+ // Colour factors, cx/y is the direction in which colours change from rgb1 to rgb2
+ cf[0] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f;
+ cf[0] = clamp(cf[0], 0.0f, 1.0f);
+ cf[1] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f;
+ cf[1] = clamp(cf[1], 0.0f, 1.0f);
+ cf[2] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f;
+ cf[2] = clamp(cf[2], 0.0f, 1.0f);
+ cf[3] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f;
+ cf[3] = clamp(cf[3], 0.0f, 1.0f);
+
+ float screenz = m_f2DNearScreenZ +
+ (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() /
+ ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z);
+
+ RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6];
+ static int indices[6] = { 0, 1, 2, 3, 0, 2 };
+ for(i = 0; i < 6; i++){
+ RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]);
+ RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]);
+ RwIm2DVertexSetScreenZ(&vert[i], screenz);
+ RwIm2DVertexSetCameraZ(&vert[i], z);
+ RwIm2DVertexSetRecipCameraZ(&vert[i], recipz);
+ RwIm2DVertexSetIntRGBA(&vert[i],
+ r1*cf[indices[i]] + r2*(1.0f - cf[indices[i]]),
+ g1*cf[indices[i]] + g2*(1.0f - cf[indices[i]]),
+ b1*cf[indices[i]] + b2*(1.0f - cf[indices[i]]),
+ a);
+ RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz);
+ RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz);
+ }
+ nSpriteBufferIndex++;
+ if(nSpriteBufferIndex >= SPRITEBUFFERSIZE)
+ FlushSpriteBuffer();
+}
+
+STARTPATCHES
+ InjectHook(0x51EE90, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&, uint32))CSprite2d::SetVertices, PATCH_JUMP);
+ InjectHook(0x51F220, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&,
+ float, float, float, float, float, float, float, float))CSprite2d::SetVertices, PATCH_JUMP);
+ InjectHook(0x51F970, (void (*)(const CRect&, const CRGBA&))CSprite2d::DrawRect, PATCH_JUMP);
+ InjectHook(0x51FA00, (void (*)(const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&))CSprite2d::DrawRect, PATCH_JUMP);
+ InjectHook(0x51FA80, CSprite2d::DrawRectXLU, PATCH_JUMP);
+
+ InjectHook(0x51C4A0, CSprite::CalcHorizonCoors, PATCH_JUMP);
+ InjectHook(0x51C3A0, CSprite::CalcScreenCoors, PATCH_JUMP);
+ InjectHook(0x51C590, CSprite::InitSpriteBuffer, PATCH_JUMP);
+ InjectHook(0x51C520, CSprite::FlushSpriteBuffer, PATCH_JUMP);
+ InjectHook(0x51C960, CSprite::RenderOneXLUSprite, PATCH_JUMP);
+ InjectHook(0x51C5D0, CSprite::RenderBufferedOneXLUSprite, PATCH_JUMP);
+ InjectHook(0x51D5B0, CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension, PATCH_JUMP);
+ InjectHook(0x51CCD0, CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect, PATCH_JUMP);
+ InjectHook(0x51D9E0, CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/Sprite.h b/src/render/Sprite.h
new file mode 100644
index 00000000..53d36de3
--- /dev/null
+++ b/src/render/Sprite.h
@@ -0,0 +1,37 @@
+#pragma once
+
+class CSprite2d
+{
+ RwTexture *m_pTexture;
+
+ static RwIm2DVertex *maVertices; //[4];
+public:
+ static float &RecipNearClip;
+
+ void SetRenderState(void);
+
+ static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far);
+ static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3,
+ float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2);
+ static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3);
+ static void DrawRect(const CRect &r, const CRGBA &col);
+ static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3);
+};
+
+class CSprite
+{
+ static float &m_f2DNearScreenZ;
+ static float &m_f2DFarScreenZ;
+ static int32 &m_bFlushSpriteBufferSwitchZTest;
+public:
+ static float CalcHorizonCoors(void);
+ static bool CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip);
+ static void InitSpriteBuffer(void);
+ static void FlushSpriteBuffer(void);
+ static void RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a);
+ static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a);
+ static void RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a);
+ static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a);
+ // cx/y is the direction in which the colour changes
+ static void RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a);
+};
diff --git a/src/render/VisibilityPlugins.cpp b/src/render/VisibilityPlugins.cpp
new file mode 100644
index 00000000..d9e87553
--- /dev/null
+++ b/src/render/VisibilityPlugins.cpp
@@ -0,0 +1,849 @@
+#include "common.h"
+#include "patcher.h"
+#include "templates.h"
+#include "Entity.h"
+#include "ModelInfo.h"
+#include "Lights.h"
+#include "Renderer.h"
+#include "VisibilityPlugins.h"
+
+#define FADE_DISTANCE 20.0f
+
+/*
+CLinkList<CVisibilityPlugins::AlphaObjectInfo> CVisibilityPlugins::m_alphaList;
+CLinkList<CVisibilityPlugins::AlphaObjectInfo> CVisibilityPlugins::m_alphaEntityList;
+
+int32 CVisibilityPlugins::ms_atomicPluginOffset = -1;
+int32 CVisibilityPlugins::ms_framePluginOffset = -1;
+int32 CVisibilityPlugins::ms_clumpPluginOffset = -1;
+*/
+CLinkList<CVisibilityPlugins::AlphaObjectInfo> &CVisibilityPlugins::m_alphaList = *(CLinkList<CVisibilityPlugins::AlphaObjectInfo>*)0x8F42E4;
+CLinkList<CVisibilityPlugins::AlphaObjectInfo> &CVisibilityPlugins::m_alphaEntityList = *(CLinkList<CVisibilityPlugins::AlphaObjectInfo>*)0x943084;
+
+int32 &CVisibilityPlugins::ms_atomicPluginOffset = *(int32*)0x600124;
+int32 &CVisibilityPlugins::ms_framePluginOffset = *(int32*)0x600128;
+int32 &CVisibilityPlugins::ms_clumpPluginOffset = *(int32*)0x60012C;
+
+RwV3d *&CVisibilityPlugins::ms_pCameraPosn = *(RwV3d**)0x8F6270;
+float &CVisibilityPlugins::ms_cullCompsDist = *(float*)0x8F2BC4;
+float &CVisibilityPlugins::ms_vehicleLod0Dist = *(float*)0x885B28;
+float &CVisibilityPlugins::ms_vehicleLod1Dist = *(float*)0x885B30;
+float &CVisibilityPlugins::ms_vehicleFadeDist = *(float*)0x8E28B4;
+float &CVisibilityPlugins::ms_bigVehicleLod0Dist = *(float*)0x8E2A84;
+float &CVisibilityPlugins::ms_bigVehicleLod1Dist = *(float*)0x8E2A8C;
+float &CVisibilityPlugins::ms_pedLod0Dist = *(float*)0x8F2BD4;
+float &CVisibilityPlugins::ms_pedLod1Dist = *(float*)0x8F2BD8;
+float &CVisibilityPlugins::ms_pedFadeDist = *(float*)0x8E2C34;
+
+void
+CVisibilityPlugins::Initialise(void)
+{
+ m_alphaList.Init(20);
+ m_alphaList.head.item.sort = 0.0f;
+ m_alphaList.tail.item.sort = 100000000.0f;
+ m_alphaEntityList.Init(350); // TODO: set back to 150 when things are fixed
+ m_alphaEntityList.head.item.sort = 0.0f;
+ m_alphaEntityList.tail.item.sort = 100000000.0f;
+}
+
+void
+CVisibilityPlugins::InitAlphaEntityList(void)
+{
+ m_alphaEntityList.Clear();
+}
+
+bool
+CVisibilityPlugins::InsertEntityIntoSortedList(CEntity *e, float dist)
+{
+ AlphaObjectInfo item;
+ item.entity = e;
+ item.sort = dist;
+ bool ret = !!m_alphaEntityList.InsertSorted(item);
+// if(!ret)
+// printf("list full %d\n", m_alphaEntityList.Count());
+ return ret;
+}
+
+void
+CVisibilityPlugins::InitAlphaAtomicList(void)
+{
+ m_alphaList.Clear();
+}
+
+bool
+CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist)
+{
+ AlphaObjectInfo item;
+ item.atomic = a;
+ item.sort = dist;
+ bool ret = !!m_alphaList.InsertSorted(item);
+// if(!ret)
+// printf("list full %d\n", m_alphaList.Count());
+ return ret;
+}
+
+RpMaterial*
+SetAlphaCB(RpMaterial *material, void *data)
+{
+ material->color.alpha = (uint8)(uint32)data;
+ return material;
+}
+
+RpMaterial*
+SetTextureCB(RpMaterial *material, void *data)
+{
+ RpMaterialSetTexture(material, (RwTexture*)data);
+ return material;
+}
+
+void
+CVisibilityPlugins::RenderAlphaAtomics(void)
+{
+ CLink<AlphaObjectInfo> *node;
+ for(node = m_alphaList.tail.prev;
+ node != &m_alphaList.head;
+ node = node->prev)
+ AtomicDefaultRenderCallBack(node->item.atomic);
+}
+
+void
+CVisibilityPlugins::RenderFadingEntities(void)
+{
+ CLink<AlphaObjectInfo> *node;
+ CSimpleModelInfo *mi;
+ for(node = m_alphaEntityList.tail.prev;
+ node != &m_alphaEntityList.head;
+ node = node->prev){
+ CEntity *e = node->item.entity;
+ if(e->m_rwObject == nil)
+ continue;
+ mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(e->m_modelIndex);
+ if(mi->m_noZwrite)
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE);
+
+ if(e->bDistanceFade){
+ DeActivateDirectional();
+ SetAmbientColours();
+ e->bImBeingRendered = true;
+ RenderFadingAtomic((RpAtomic*)e->m_rwObject, node->item.sort);
+ e->bImBeingRendered = false;
+ }else
+ CRenderer::RenderOneNonRoad(e);
+
+ if(mi->m_noZwrite)
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
+ }
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic)
+{
+ RpAtomic *lodatm;
+ RwMatrix *m;
+ RwV3d view;
+ float len;
+ CSimpleModelInfo *mi;
+
+ mi = GetAtomicModelInfo(atomic);
+ m = RwFrameGetLTM(RpAtomicGetFrame(atomic));
+ RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn);
+ len = RwV3dLength(&view);
+ lodatm = mi->GetAtomicFromDistance(len);
+ if(lodatm){
+ if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic))
+ RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE);
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderObjNormalAtomic(RpAtomic *atomic)
+{
+ RwMatrix *m;
+ RwV3d view;
+ float len;
+
+ m = RwFrameGetLTM(RpAtomicGetFrame(atomic));
+ RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn);
+ len = RwV3dLength(&view);
+ if(RwV3dDotProduct(&view, RwMatrixGetUp(m)) < -0.3f*len && len > 8.0f)
+ return atomic;
+ AtomicDefaultRenderCallBack(atomic);
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderAlphaAtomic(RpAtomic *atomic, int alpha)
+{
+ RpGeometry *geo;
+ uint32 flags;
+
+ geo = RpAtomicGetGeometry(atomic);
+ flags = RpGeometryGetFlags(geo);
+ RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR);
+ RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha);
+ AtomicDefaultRenderCallBack(atomic);
+ RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255);
+ RpGeometrySetFlags(geo, flags);
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist)
+{
+ RpAtomic *lodatm;
+ float fadefactor;
+ uint8 alpha;
+ CSimpleModelInfo *mi;
+
+ mi = GetAtomicModelInfo(atomic);
+ lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE);
+ if(mi->m_additive){
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+ AtomicDefaultRenderCallBack(atomic);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+ }else{
+ fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE;
+ if(fadefactor > 1.0f)
+ fadefactor = 1.0f;
+ alpha = mi->m_alpha * fadefactor;
+ if(alpha == 255)
+ AtomicDefaultRenderCallBack(atomic);
+ else{
+ RpGeometry *geo = RpAtomicGetGeometry(lodatm);
+ uint32 flags = RpGeometryGetFlags(geo);
+ RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR);
+ RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha);
+ if(geo != RpAtomicGetGeometry(atomic))
+ RpAtomicSetGeometry(atomic, geo, 0);
+ AtomicDefaultRenderCallBack(atomic);
+ RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255);
+ RpGeometrySetFlags(geo, flags);
+ }
+ }
+ return atomic;
+}
+
+
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_vehicleLod0Dist){
+ flags = GetAtomicId(atomic);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
+ return atomic;
+ }
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_vehicleLod0Dist){
+ flags = GetAtomicId(atomic);
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
+ if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
+ return atomic;
+
+ if(flags & ATOMIC_FLAG_DRAWLAST){
+ // sort before clump
+ if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f))
+ AtomicDefaultRenderCallBack(atomic);
+ }else{
+ if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_bigVehicleLod0Dist){
+ flags = GetAtomicId(atomic);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f)
+ return atomic;
+ }
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_bigVehicleLod0Dist){
+ flags = GetAtomicId(atomic);
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f)
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
+ return atomic;
+
+ if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_bigVehicleLod1Dist)
+ AtomicDefaultRenderCallBack(atomic);
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq >= ms_bigVehicleLod0Dist &&
+ distsq < ms_bigVehicleLod1Dist){
+ flags = GetAtomicId(atomic);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f)
+ return atomic;
+ }
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq >= ms_bigVehicleLod0Dist &&
+ distsq < ms_bigVehicleLod1Dist){
+ flags = GetAtomicId(atomic);
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f)
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
+ return atomic;
+
+ if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic)
+{
+ RpClump *clump;
+ float dist;
+ int32 alpha;
+
+ clump = RpAtomicGetClump(atomic);
+ dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
+ if(dist >= ms_vehicleLod0Dist){
+ alpha = GetClumpAlpha(clump);
+ if(alpha == 255)
+ AtomicDefaultRenderCallBack(atomic);
+ else
+ RenderAlphaAtomic(atomic, alpha);
+ }
+ return atomic;
+
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq >= ms_bigVehicleLod1Dist)
+ AtomicDefaultRenderCallBack(atomic);
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_bigVehicleLod1Dist){
+ flags = GetAtomicId(atomic);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
+ return atomic;
+ }
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic)
+{
+ RwFrame *clumpframe;
+ float distsq, dot;
+ uint32 flags;
+
+ clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
+ distsq = GetDistanceSquaredFromCamera(clumpframe);
+ if(distsq < ms_bigVehicleLod1Dist){
+ flags = GetAtomicId(atomic);
+ dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
+ RwFrameGetLTM(clumpframe), flags);
+ if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
+ if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
+ return atomic;
+
+ if(flags & ATOMIC_FLAG_DRAWLAST){
+ // sort before clump
+ if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f))
+ AtomicDefaultRenderCallBack(atomic);
+ }else{
+ if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
+ AtomicDefaultRenderCallBack(atomic);
+ }
+ }
+ return atomic;
+}
+
+// TODO: this is part of a struct
+static RwTexture *&playerskin = *(RwTexture**)0x941428;
+
+RpAtomic*
+CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic)
+{
+ if(playerskin)
+ RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, playerskin);
+ AtomicDefaultRenderCallBack(atomic);
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderPedLowDetailCB(RpAtomic *atomic)
+{
+ RpClump *clump;
+ float dist;
+ int32 alpha;
+
+ clump = RpAtomicGetClump(atomic);
+ dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
+ if(dist >= ms_pedLod0Dist){
+ alpha = GetClumpAlpha(clump);
+ if(alpha == 255)
+ AtomicDefaultRenderCallBack(atomic);
+ else
+ RenderAlphaAtomic(atomic, alpha);
+ }
+ return atomic;
+}
+
+RpAtomic*
+CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic)
+{
+ RpClump *clump;
+ float dist;
+ int32 alpha;
+
+ clump = RpAtomicGetClump(atomic);
+ dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
+ if(dist < ms_pedLod0Dist){
+ alpha = GetClumpAlpha(clump);
+ if(alpha == 255)
+ AtomicDefaultRenderCallBack(atomic);
+ else
+ RenderAlphaAtomic(atomic, alpha);
+ }
+ return atomic;
+}
+
+float
+CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame)
+{
+ RwMatrix *m;
+ RwV3d dist;
+ m = RwFrameGetLTM(frame);
+ RwV3dSub(&dist, RwMatrixGetPos(m), ms_pCameraPosn);
+ return RwV3dDotProduct(&dist, &dist);
+}
+
+float
+CVisibilityPlugins::GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags)
+{
+ RwV3d dist;
+ float dot, dotdoor;
+
+ // Vehicle forward is the y axis (RwMatrix.up)
+ // Vehicle right is the x axis (RwMatrix.right)
+
+ RwV3dSub(&dist, RwMatrixGetPos(atomicMat), ms_pCameraPosn);
+ // forward/backward facing
+ if(flags & (ATOMIC_FLAG_FRONT | ATOMIC_FLAG_REAR))
+ dot = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
+ // left/right facing
+ else if(flags & (ATOMIC_FLAG_LEFT & ATOMIC_FLAG_RIGHT))
+ dot = RwV3dDotProduct(&dist, RwMatrixGetRight(clumpMat));
+ else
+ dot = 0.0f;
+ if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_REAR))
+ dot = -dot;
+
+ if(flags & (ATOMIC_FLAG_REARDOOR | ATOMIC_FLAG_FRONTDOOR)){
+ if(flags & ATOMIC_FLAG_REARDOOR)
+ dotdoor = -RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
+ else if(flags & ATOMIC_FLAG_FRONTDOOR)
+ dotdoor = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
+ else
+ dotdoor = 0.0f;
+
+ if(dot < 0.0f && dotdoor < 0.0f)
+ dot += dotdoor;
+ if(dot > 0.0f && dotdoor > 0.0f)
+ dot += dotdoor;
+ }
+
+ return dot;
+}
+
+/* These are all unused */
+
+bool
+CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump)
+{
+ return true;
+}
+
+bool
+CVisibilityPlugins::FrustumSphereCB(RpClump *clump)
+{
+ // TODO, but unused
+ return true;
+}
+
+bool
+CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump)
+{
+ // TODO, but unused
+ return true;
+}
+
+bool
+CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump)
+{
+ // TODO, but unused
+ return true;
+}
+
+
+
+
+//
+// RW Plugins
+//
+
+enum
+{
+ ID_VISIBILITYATOMIC = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x00),
+ ID_VISIBILITYCLUMP = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x01),
+ ID_VISIBILITYFRAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x02),
+};
+
+bool
+CVisibilityPlugins::PluginAttach(void)
+{
+ ms_atomicPluginOffset = RpAtomicRegisterPlugin(sizeof(AtomicExt),
+ ID_VISIBILITYATOMIC,
+ AtomicConstructor, AtomicDestructor, AtomicCopyConstructor);
+
+ ms_framePluginOffset = RwFrameRegisterPlugin(sizeof(FrameExt),
+ ID_VISIBILITYFRAME,
+ FrameConstructor, FrameDestructor, FrameCopyConstructor);
+
+ ms_clumpPluginOffset = RpClumpRegisterPlugin(sizeof(ClumpExt),
+ ID_VISIBILITYCLUMP,
+ ClumpConstructor, ClumpDestructor, ClumpCopyConstructor);
+ return ms_atomicPluginOffset != -1 && ms_clumpPluginOffset != -1;
+}
+
+#define ATOMICEXT(o) (RWPLUGINOFFSET(AtomicExt, o, ms_atomicPluginOffset))
+#define FRAMEEXT(o) (RWPLUGINOFFSET(FrameExt, o, ms_framePluginOffset))
+#define CLUMPEXT(o) (RWPLUGINOFFSET(ClumpExt, o, ms_clumpPluginOffset))
+
+//
+// Atomic
+//
+
+void*
+CVisibilityPlugins::AtomicConstructor(void *object, int32, int32)
+{
+ ATOMICEXT(object)->modelInfo = nil;
+ return object;
+}
+
+void*
+CVisibilityPlugins::AtomicDestructor(void *object, int32, int32)
+{
+ return object;
+}
+
+void*
+CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32)
+{
+ *ATOMICEXT(dst) = *ATOMICEXT(src);
+ return dst;
+}
+
+void
+CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic,
+ CSimpleModelInfo *modelInfo)
+{
+ AtomicExt *ext = ATOMICEXT(atomic);
+ ext->modelInfo = modelInfo;
+ switch(modelInfo->m_type)
+ case MITYPE_SIMPLE:
+ case MITYPE_TIME:
+ if(modelInfo->m_normalCull)
+ SetAtomicRenderCallback(atomic, RenderObjNormalAtomic);
+}
+
+CSimpleModelInfo*
+CVisibilityPlugins::GetAtomicModelInfo(RpAtomic *atomic)
+{
+ return ATOMICEXT(atomic)->modelInfo;
+}
+
+void
+CVisibilityPlugins::SetAtomicFlag(RpAtomic *atomic, int f)
+{
+ ATOMICEXT(atomic)->flags |= f;
+}
+
+void
+CVisibilityPlugins::ClearAtomicFlag(RpAtomic *atomic, int f)
+{
+ ATOMICEXT(atomic)->flags &= ~f;
+}
+
+int
+CVisibilityPlugins::GetAtomicId(RpAtomic *atomic)
+{
+ return ATOMICEXT(atomic)->flags;
+}
+
+// This is rather useless, but whatever
+void
+CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb)
+{
+ if(cb == nil)
+ cb = AtomicDefaultRenderCallBack; // not necessary
+ RpAtomicSetRenderCallBack(atomic, cb);
+}
+
+//
+// Frame
+//
+
+void*
+CVisibilityPlugins::FrameConstructor(void *object, int32, int32)
+{
+ FRAMEEXT(object)->id = 0;
+ return object;
+}
+
+void*
+CVisibilityPlugins::FrameDestructor(void *object, int32, int32)
+{
+ return object;
+}
+
+void*
+CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32)
+{
+ *FRAMEEXT(dst) = *FRAMEEXT(src);
+ return dst;
+}
+
+void
+CVisibilityPlugins::SetFrameHierarchyId(RwFrame *frame, int32 id)
+{
+ FRAMEEXT(frame)->id = id;
+}
+
+int32
+CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame)
+{
+ return FRAMEEXT(frame)->id;
+}
+
+
+//
+// Clump
+//
+
+void*
+CVisibilityPlugins::ClumpConstructor(void *object, int32, int32)
+{
+ ClumpExt *ext = CLUMPEXT(object);
+ ext->visibilityCB = DefaultVisibilityCB;
+ ext->alpha = 0xFF;
+ return object;
+}
+
+void*
+CVisibilityPlugins::ClumpDestructor(void *object, int32, int32)
+{
+ return object;
+}
+
+void*
+CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32)
+{
+ CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB;
+ return dst;
+}
+
+void
+CVisibilityPlugins::SetClumpModelInfo(RpClump *clump, CClumpModelInfo *modelInfo)
+{
+ CVehicleModelInfo *vmi;
+ SetFrameHierarchyId(RpClumpGetFrame(clump), (int32)modelInfo);
+
+ // Unused
+ switch(modelInfo->m_type){
+ // ignore MLO
+ case MITYPE_VEHICLE:
+ vmi = (CVehicleModelInfo*)modelInfo;
+ if(vmi->m_vehicleType == VEHICLE_TYPE_TRAIN ||
+ vmi->m_vehicleType == VEHICLE_TYPE_HELI ||
+ vmi->m_vehicleType == VEHICLE_TYPE_PLANE)
+ CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB_BigVehicle;
+ else
+ CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB;
+ break;
+ }
+}
+
+void
+CVisibilityPlugins::SetClumpAlpha(RpClump *clump, int alpha)
+{
+ CLUMPEXT(clump)->alpha = alpha;
+}
+
+int
+CVisibilityPlugins::GetClumpAlpha(RpClump *clump)
+{
+ return CLUMPEXT(clump)->alpha;
+}
+
+
+STARTPATCHES
+ InjectHook(0x527E50, CVisibilityPlugins::Initialise, PATCH_JUMP);
+ InjectHook(0x528F90, CVisibilityPlugins::InitAlphaEntityList, PATCH_JUMP);
+ InjectHook(0x528FF0, CVisibilityPlugins::InsertEntityIntoSortedList, PATCH_JUMP);
+ InjectHook(0x528F80, CVisibilityPlugins::InitAlphaAtomicList, PATCH_JUMP);
+ InjectHook(0x528FA0, CVisibilityPlugins::InsertAtomicIntoSortedList, PATCH_JUMP);
+ InjectHook(0x527F60, SetAlphaCB, PATCH_JUMP);
+ InjectHook(0x529040, CVisibilityPlugins::RenderAlphaAtomics, PATCH_JUMP);
+ InjectHook(0x529070, CVisibilityPlugins::RenderFadingEntities, PATCH_JUMP);
+
+ InjectHook(0x527F70, CVisibilityPlugins::RenderWheelAtomicCB, PATCH_JUMP);
+ InjectHook(0x528000, CVisibilityPlugins::RenderObjNormalAtomic, PATCH_JUMP);
+ InjectHook(0x5280B0, CVisibilityPlugins::RenderAlphaAtomic, PATCH_JUMP);
+ InjectHook(0x528100, CVisibilityPlugins::RenderFadingAtomic, PATCH_JUMP);
+
+ InjectHook(0x5283E0, CVisibilityPlugins::RenderVehicleHiDetailCB, PATCH_JUMP);
+ InjectHook(0x5284B0, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB, PATCH_JUMP);
+ InjectHook(0x5288A0, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x528A10, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x528AD0, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat, PATCH_JUMP);
+ InjectHook(0x5287F0, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x528940, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x528240, CVisibilityPlugins::RenderVehicleReallyLowDetailCB, PATCH_JUMP);
+ InjectHook(0x5287B0, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle, PATCH_JUMP);
+ InjectHook(0x5285D0, CVisibilityPlugins::RenderTrainHiDetailCB, PATCH_JUMP);
+ InjectHook(0x5286A0, CVisibilityPlugins::RenderTrainHiDetailAlphaCB, PATCH_JUMP);
+
+ InjectHook(0x528BC0, CVisibilityPlugins::RenderPedHiDetailCB, PATCH_JUMP);
+ InjectHook(0x528B60, CVisibilityPlugins::RenderPedLowDetailCB, PATCH_JUMP);
+
+
+ InjectHook(0x527DC0, CVisibilityPlugins::PluginAttach, PATCH_JUMP);
+
+ InjectHook(0x527EC0, CVisibilityPlugins::SetAtomicModelInfo, PATCH_JUMP);
+ InjectHook(0x527F00, CVisibilityPlugins::GetAtomicModelInfo, PATCH_JUMP);
+ InjectHook(0x527F10, CVisibilityPlugins::SetAtomicFlag, PATCH_JUMP);
+ InjectHook(0x527F30, CVisibilityPlugins::ClearAtomicFlag, PATCH_JUMP);
+ InjectHook(0x527F50, CVisibilityPlugins::GetAtomicId, PATCH_JUMP);
+ InjectHook(0x528C20, CVisibilityPlugins::SetAtomicRenderCallback, PATCH_JUMP);
+
+ InjectHook(0x528D60, CVisibilityPlugins::SetFrameHierarchyId, PATCH_JUMP);
+ InjectHook(0x528D80, CVisibilityPlugins::GetFrameHierarchyId, PATCH_JUMP);
+
+ InjectHook(0x528ED0, CVisibilityPlugins::SetClumpModelInfo, PATCH_JUMP);
+ InjectHook(0x528F50, CVisibilityPlugins::SetClumpAlpha, PATCH_JUMP);
+ InjectHook(0x528F70, CVisibilityPlugins::GetClumpAlpha, PATCH_JUMP);
+
+
+ InjectHook(0x529120, CVisibilityPlugins::GetDistanceSquaredFromCamera, PATCH_JUMP);
+ InjectHook(0x5282A0, CVisibilityPlugins::GetDotProductWithCameraVector, PATCH_JUMP);
+ENDPATCHES
diff --git a/src/render/VisibilityPlugins.h b/src/render/VisibilityPlugins.h
new file mode 100644
index 00000000..f041b24e
--- /dev/null
+++ b/src/render/VisibilityPlugins.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#include "templates.h"
+
+class CEntity;
+class CSimpleModelInfo;
+class CClumpModelInfo;
+
+typedef bool (*ClumpVisibilityCB)(RpClump*);
+
+class CVisibilityPlugins
+{
+public:
+ struct AlphaObjectInfo
+ {
+ union {
+ CEntity *entity;
+ RpAtomic *atomic;
+ };
+ float sort;
+ };
+
+ static CLinkList<AlphaObjectInfo> &m_alphaList;
+ static CLinkList<AlphaObjectInfo> &m_alphaEntityList;
+ static RwV3d *&ms_pCameraPosn;
+ static float &ms_cullCompsDist;
+ static float &ms_vehicleLod0Dist;
+ static float &ms_vehicleLod1Dist;
+ static float &ms_vehicleFadeDist;
+ static float &ms_bigVehicleLod0Dist;
+ static float &ms_bigVehicleLod1Dist;
+ static float &ms_pedLod0Dist;
+ static float &ms_pedLod1Dist;
+ static float &ms_pedFadeDist;
+
+ static void Initialise(void);
+ static void InitAlphaEntityList(void);
+ static bool InsertEntityIntoSortedList(CEntity *e, float dist);
+ static void InitAlphaAtomicList(void);
+ static bool InsertAtomicIntoSortedList(RpAtomic *a, float dist);
+
+ static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic);
+ static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic);
+ static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha);
+ static RpAtomic *RenderFadingAtomic(RpAtomic *atm, float dist);
+
+ static RpAtomic *RenderVehicleHiDetailCB(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleHiDetailAlphaCB(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleHiDetailCB_Boat(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleReallyLowDetailCB(RpAtomic *atomic);
+ static RpAtomic *RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic);
+ static RpAtomic *RenderTrainHiDetailCB(RpAtomic *atomic);
+ static RpAtomic *RenderTrainHiDetailAlphaCB(RpAtomic *atomic);
+
+ static RpAtomic *RenderPlayerCB(RpAtomic *atomic);
+ static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic);
+ static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic);
+
+ static void RenderAlphaAtomics(void);
+ static void RenderFadingEntities(void);
+
+ // All actually unused
+ static bool DefaultVisibilityCB(RpClump *clump);
+ static bool FrustumSphereCB(RpClump *clump);
+// static bool MloVisibilityCB(RpClump *clump);
+ static bool VehicleVisibilityCB(RpClump *clump);
+ static bool VehicleVisibilityCB_BigVehicle(RpClump *clump);
+
+ static float GetDistanceSquaredFromCamera(RwFrame *frame);
+ static float GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags);
+
+ //
+ // RW Plugins
+ //
+
+ union AtomicExt
+ {
+ CSimpleModelInfo *modelInfo; // used by SimpleModelInfo
+ int flags; // used by ClumpModelInfo
+ };
+ static void SetAtomicModelInfo(RpAtomic*, CSimpleModelInfo*);
+ static CSimpleModelInfo *GetAtomicModelInfo(RpAtomic *atomic);
+ static void SetAtomicFlag(RpAtomic*, int);
+ static void ClearAtomicFlag(RpAtomic*, int);
+ static int GetAtomicId(RpAtomic *atomic);
+ static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender);
+
+ static void *AtomicConstructor(void *object, int32 offset, int32 len);
+ static void *AtomicDestructor(void *object, int32 offset, int32 len);
+ static void *AtomicCopyConstructor(void *dst, const void *src,
+ int32 offset, int32 len);
+ static int32 &ms_atomicPluginOffset;
+
+ struct FrameExt
+ {
+ // BUG: this is abused to hold a pointer by SetClumpModelInfo
+ int32 id;
+ };
+ static void SetFrameHierarchyId(RwFrame *frame, int32 id);
+ static int32 GetFrameHierarchyId(RwFrame *frame);
+
+ static void *FrameConstructor(void *object, int32 offset, int32 len);
+ static void *FrameDestructor(void *object, int32 offset, int32 len);
+ static void *FrameCopyConstructor(void *dst, const void *src,
+ int32 offset, int32 len);
+ static int32 &ms_framePluginOffset;
+
+ // Not actually used
+ struct ClumpExt
+ {
+ ClumpVisibilityCB visibilityCB;
+ int alpha;
+ };
+ static void SetClumpModelInfo(RpClump*, CClumpModelInfo*);
+ static void SetClumpAlpha(RpClump*, int);
+ static int GetClumpAlpha(RpClump*);
+
+ static void *ClumpConstructor(void *object, int32 offset, int32 len);
+ static void *ClumpDestructor(void *object, int32 offset, int32 len);
+ static void *ClumpCopyConstructor(void *dst, const void *src,
+ int32 offset, int32 len);
+ static int32 &ms_clumpPluginOffset;
+
+ static bool PluginAttach(void);
+};
diff --git a/src/rw.cpp b/src/rw.cpp
new file mode 100644
index 00000000..13383783
--- /dev/null
+++ b/src/rw.cpp
@@ -0,0 +1,285 @@
+#include "common.h"
+#include "patcher.h"
+#include <rwcore.h>
+#include <rpworld.h>
+#include <rpmatfx.h>
+
+//
+ // ADDRESS
+//
+
+int gtaversion = -1;
+
+WRAPPER RwTexDictionary *RwTexDictionaryCreate(void) { EAXJMP(0x5A7160); }
+WRAPPER RwBool RwTexDictionaryDestroy(RwTexDictionary*) { EAXJMP(0x5A7200); }
+static uint32_t RwTexDictionaryFindNamedTexture_A = AddressByVersion<uint32_t>(0x5A74D0, 0, 0, 0x64E060, 0, 0);
+WRAPPER RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary*, const RwChar*) { VARJMP(RwTexDictionaryFindNamedTexture_A); }
+static uint32_t RwTextureRead_A = AddressByVersion<uint32_t>(0x5A7580, 0x5A7840, 0x5A8E00, 0x64E110, 0, 0);
+WRAPPER RwTexture *RwTextureRead(const RwChar*, const RwChar*) { VARJMP(RwTextureRead_A); }
+static uint32_t RwTexDictionaryGetCurrent_A = AddressByVersion<uint32_t>(0x5A7570, 0x5A7830, 0x5A8DA0, 0x64E100, 0, 0);
+WRAPPER RwTexDictionary *RwTexDictionaryGetCurrent(void) { VARJMP(RwTexDictionaryGetCurrent_A); }
+static uint32_t RwTexDictionarySetCurrent_A = AddressByVersion<uint32_t>(0x5A7550, 0x5A7810, 0x5A8D80, 0x64E0E0, 0, 0);
+WRAPPER RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict) { VARJMP(RwTexDictionarySetCurrent_A); }
+static uint32_t RwTexDictionaryForAllTextures_A = AddressByVersion<uint32_t>(0, 0, 0, 0x64DE20, 0, 0);
+WRAPPER const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary*, RwTextureCallBack, void*) { VARJMP(RwTexDictionaryForAllTextures_A); }
+
+static uint32_t RwV3dLength_A = AddressByVersion<uint32_t>(0x5A36A0, 0, 0, 0x647030, 0, 0);
+WRAPPER RwReal RwV3dLength(const RwV3d*) { VARJMP(RwV3dLength_A); }
+WRAPPER RwV3d *RwV3dTransformPoints(RwV3d*, const RwV3d*, RwInt32, const RwMatrix*) { EAXJMP(0x5A37D0); }
+
+static uint32_t D3D8AtomicDefaultInstanceCallback_A = AddressByVersion<uint32_t>(0x5DB450, 0x5DB710, 0x5EC520, 0x67BAE0, 0, 0);
+WRAPPER RwBool D3D8AtomicDefaultInstanceCallback(void*, RxD3D8InstanceData*, RwBool) { VARJMP(D3D8AtomicDefaultInstanceCallback_A); }
+static uint32_t D3D8AtomicDefaultReinstanceCallback_A = AddressByVersion<uint32_t>(0x5DBFB0, 0, 0, 0x67C640, 0, 0);
+WRAPPER RwBool D3D8AtomicDefaultReinstanceCallback(void*, RwResEntry*, const RpMeshHeader*, RxD3D8AllInOneInstanceCallBack) { VARJMP(D3D8AtomicDefaultReinstanceCallback_A); }
+
+static uint32_t rwD3D8RWGetRasterStage_A = AddressByVersion<uint32_t>(0x5B5390, 0x5B5650, 0x5BA2C0, 0x659840, 0x659890, 0x6587F0);
+WRAPPER int rwD3D8RWGetRasterStage(int) { VARJMP(rwD3D8RWGetRasterStage_A); }
+
+static uint32_t RpWorldAddCamera_A = AddressByVersion<uint32_t>(0x5AFB80, 0, 0, 0x654460, 0, 0);
+WRAPPER RpWorld *RpWorldAddCamera(RpWorld*, RwCamera*) { VARJMP(RpWorldAddCamera_A); }
+
+static uint32_t RpMaterialRegisterPlugin_A = AddressByVersion<uint32_t>(0x5ADD40, 0, 0, 0x6558C0, 0, 0);
+WRAPPER RwInt32 RpMaterialRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { VARJMP(RpMaterialRegisterPlugin_A); }
+WRAPPER RpMaterial *RpMaterialSetTexture(RpMaterial*, RwTexture*) { EAXJMP(0x5ADD10); }
+
+//
+ //
+//
+
+static uint32_t RwMatrixCreate_A = AddressByVersion<uint32_t>(0x5A3330, 0x5A35F0, 0x5A3FA0, 0x644620, 0x644670, 0x6435D0);
+WRAPPER RwMatrix *RwMatrixCreate(void) { VARJMP(RwMatrixCreate_A); }
+static uint32_t RwMatrixDestroy_A = AddressByVersion<uint32_t>(0x5A3300, 0x5A35C0, 0x5A3F70, 0x6445F0, 0x644640, 0x6435A0);
+WRAPPER RwBool RwMatrixDestroy(RwMatrix*) { VARJMP(RwMatrixDestroy_A); }
+static uint32_t RwMatrixMultiply_A = AddressByVersion<uint32_t>(0x5A28F0, 0x5A2BB0, 0x5A2E10, 0x6437C0, 0x643810, 0x642770);
+WRAPPER RwMatrix *RwMatrixMultiply(RwMatrix*, const RwMatrix*, const RwMatrix*) { VARJMP(RwMatrixMultiply_A); }
+static uint32_t RwMatrixInvert_A = AddressByVersion<uint32_t>(0x5A2C90, 0x5A2F50, 0x5A35A0, 0x643F40, 0x643F90, 0x642EF0);
+WRAPPER RwMatrix *RwMatrixInvert(RwMatrix*, const RwMatrix*) { VARJMP(RwMatrixInvert_A); }
+static uint32_t RwMatrixUpdate_A = AddressByVersion<uint32_t>(0x5A28E0, 0x5A2BA0, 0x5A2DF0, 0x6437B0, 0x643800, 0x642760);
+WRAPPER RwMatrix *RwMatrixUpdate(RwMatrix*) { VARJMP(RwMatrixUpdate_A); }
+static uint32_t RwMatrixRotate_A = AddressByVersion<uint32_t>(0x5A2BF0, 0x5A2EB0, 0x5A3510, 0x643EA0, 0x643EF0, 0x642E50);
+WRAPPER RwMatrix *RwMatrixRotate(RwMatrix*, const RwV3d*, RwReal, RwOpCombineType) { VARJMP(RwMatrixRotate_A); }
+static uint32_t RwMatrixOptimize_A = AddressByVersion<uint32_t>(0x5A2820, 0, 0, 0x6436F0, 0, 0);
+WRAPPER RwMatrix *RwMatrixOptimize(RwMatrix *matrix, const RwMatrixTolerance *tolerance) { VARJMP(RwMatrixOptimize_A); }
+WRAPPER RwMatrix *RwMatrixTransform(RwMatrix*, const RwMatrix*, RwOpCombineType) { EAXJMP(0x5A31C0); }
+WRAPPER RwFrame *RwFrameForAllObjects(RwFrame*, RwObjectCallBack, void*) { EAXJMP(0x5A2340); }
+
+static uint32_t RwFrameCreate_A = AddressByVersion<uint32_t>(0x5A1A00, 0x5A1CC0, 0x5A2270, 0x644AA0, 0x644AF0, 0x643A50);
+WRAPPER RwFrame *RwFrameCreate(void) { VARJMP(RwFrameCreate_A); }
+static uint32_t RwFrameUpdateObjects_A = AddressByVersion<uint32_t>(0x5A1C60, 0x5A1F20, 0x5A23B4, 0x644D00, 0x644D50, 0x643CB0);
+WRAPPER RwFrame *RwFrameUpdateObjects(RwFrame* a1)
+{
+ if (gtaversion != III_STEAM)
+ VARJMP(RwFrameUpdateObjects_A);
+
+ __asm
+ {
+ mov eax, a1
+ jmp RwFrameUpdateObjects_A
+ }
+}
+static uint32_t RwFrameGetLTM_A = AddressByVersion<uint32_t>(0x5A1CE0, 0x5A1FA0, 0x5A2430, 0x644D80, 0x644DD0, 0x643D30);
+WRAPPER RwMatrix *RwFrameGetLTM(RwFrame*) { VARJMP(RwFrameGetLTM_A); }
+static uint32_t RwFrameTransform_A = AddressByVersion<uint32_t>(0x5A2140, 0x5A2400, 0x5A26E0, 0x6451E0, 0x645230, 0x644190);
+WRAPPER RwFrame *RwFrameTransform(RwFrame*, const RwMatrix*, RwOpCombineType) { VARJMP(RwFrameTransform_A); }
+WRAPPER RwBool RwFrameDestroy(RwFrame*) { EAXJMP(0x5A1A30); }
+WRAPPER RwInt32 RwFrameRegisterPlugin(RwInt32, RwUInt32,RwPluginObjectConstructor,RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A2380); }
+WRAPPER RwFrame *RwFrameForAllChildren(RwFrame*, RwFrameCallBack, void*) { EAXJMP(0x5A1FC0); }
+WRAPPER RwFrame *RwFrameAddChild(RwFrame*, RwFrame*) { EAXJMP(0x5A1D00); }
+WRAPPER RwFrame *RwFrameRemoveChild(RwFrame*) { EAXJMP(0x5A1ED0); }
+WRAPPER RwFrame *RwFrameScale(RwFrame*, const RwV3d*, RwOpCombineType) { EAXJMP(0x5A20A0); }
+
+static uint32_t RwCameraCreate_A = AddressByVersion<uint32_t>(0x5A5360, 0x5A5620, 0x5A74E0, 0x64AB50, 0x64ABA0, 0x649B00);
+WRAPPER RwCamera *RwCameraCreate(void) { VARJMP(RwCameraCreate_A); }
+static uint32_t RwCameraBeginUpdate_A = AddressByVersion<uint32_t>(0x5A5030, 0x5A52F0, 0x5A7220, 0x64A820, 0x64A870, 0x6497D0);
+WRAPPER RwCamera *RwCameraBeginUpdate(RwCamera*) { VARJMP(RwCameraBeginUpdate_A); }
+static uint32_t RwCameraEndUpdate_A = AddressByVersion<uint32_t>(0x5A5020, 0x5A52E0, 0x5A7200, 0x64A810, 0x64A860, 0x6497C0);
+WRAPPER RwCamera *RwCameraEndUpdate(RwCamera*) { VARJMP(RwCameraEndUpdate_A); }
+static uint32_t RwCameraSetNearClipPlane_A = AddressByVersion<uint32_t>(0x5A5070, 0x5A5330, 0x5A7270, 0x64A860, 0x64A8B0, 0x649810);
+WRAPPER RwCamera *RwCameraSetNearClipPlane(RwCamera*, RwReal) { VARJMP(RwCameraSetNearClipPlane_A); }
+static uint32_t RwCameraSetFarClipPlane_A = AddressByVersion<uint32_t>(0x5A5140, 0x5A5400, 0x5A72A0, 0x64A930, 0x64A980, 0x6498E0);
+WRAPPER RwCamera *RwCameraSetFarClipPlane(RwCamera*, RwReal) { VARJMP(RwCameraSetFarClipPlane_A); }
+static uint32_t RwCameraSetViewWindow_A = AddressByVersion<uint32_t>(0x5A52B0, 0x5A5570, 0x5A7440, 0x64AAA0, 0x64AAF0, 0x649A50);
+WRAPPER RwCamera *RwCameraSetViewWindow(RwCamera*, const RwV2d*) { VARJMP(RwCameraSetViewWindow_A); }
+static uint32_t RwCameraClear_A = AddressByVersion<uint32_t>(0x5A51E0, 0x5A54A0, 0x5A7350, 0x64A9D0, 0x64AA20, 0x649980);
+WRAPPER RwCamera *RwCameraClear(RwCamera*, RwRGBA*, RwInt32) { VARJMP(RwCameraClear_A); }
+
+static uint32_t RwRasterCreate_A = AddressByVersion<uint32_t>(0x5AD930, 0x5ADBF0, 0x5B0580, 0x655490, 0x6554E0, 0x654440);
+WRAPPER RwRaster *RwRasterCreate(RwInt32, RwInt32, RwInt32, RwInt32) { VARJMP(RwRasterCreate_A); }
+// ADDRESS
+static uint32_t RwRasterDestroy_A = AddressByVersion<uint32_t>(0x5AD780, 0, 0, 0x6552E0, 0, 0);
+WRAPPER RwBool RwRasterDestroy(RwRaster*) { VARJMP(RwRasterDestroy_A); }
+static uint32_t RwRasterSetFromImage_A = AddressByVersion<uint32_t>(0x5BBF50, 0x5BC210, 0x5C0BF0, 0x6602B0, 0x660300, 0x65F260);
+WRAPPER RwRaster *RwRasterSetFromImage(RwRaster*, RwImage*) { VARJMP(RwRasterSetFromImage_A); }
+static uint32_t RwRasterPushContext_A = AddressByVersion<uint32_t>(0x5AD7C0, 0x5ADA80, 0x5B03C0, 0x655320, 0x655370, 0x6542D0);
+WRAPPER RwRaster *RwRasterPushContext(RwRaster*) { VARJMP(RwRasterPushContext_A); }
+static uint32_t RwRasterPopContext_A = AddressByVersion<uint32_t>(0x5AD870, 0x5ADB30, 0x5B0460, 0x6553D0, 0x655420, 0x654380);
+WRAPPER RwRaster *RwRasterPopContext(void) { VARJMP(RwRasterPopContext_A); }
+static uint32_t RwRasterRenderFast_A = AddressByVersion<uint32_t>(0x5AD710, 0x5AD9D0, 0x5B0800, 0x655270, 0x6552C0, 0x654220);
+WRAPPER RwRaster *RwRasterRenderFast(RwRaster*, RwInt32, RwInt32) { VARJMP(RwRasterRenderFast_A); }
+//RwRaster *RwRasterRenderScaled(RwRaster *raster, RwRect *rect)
+//{
+// ((RwGlobals*)RwEngineInst)->stdFunc[rwSTANDARDRASTERRENDERSCALED](raster, rect, 0);
+// return raster;
+//}
+
+static uint32_t RwTextureCreate_A = AddressByVersion<uint32_t>(0x5A72D0, 0x5A7590, 0x5A8AC0, 0x64DE60, 0x64DEB0, 0x64CE10);
+WRAPPER RwTexture *RwTextureCreate(RwRaster*) { VARJMP(RwTextureCreate_A); }
+WRAPPER RwBool RwTextureDestroy(RwTexture*) { EAXJMP(0x5A7330); }
+
+static uint32_t RwRenderStateGet_A = AddressByVersion<uint32_t>(0x5A4410, 0x5A46D0, 0x5A53B0, 0x649BF0, 0x649C40, 0x648BA0);
+WRAPPER RwBool RwRenderStateGet(RwRenderState, void*) { VARJMP(RwRenderStateGet_A); }
+static uint32_t RwRenderStateSet_A = AddressByVersion<uint32_t>(0x5A43C0, 0x5A4680, 0x5A5360, 0x649BA0, 0x649BF0, 0x648B50);
+WRAPPER RwBool RwRenderStateSet(RwRenderState, void*) { VARJMP(RwRenderStateSet_A); }
+
+static uint32_t RwD3D8SetTexture_A = AddressByVersion<uint32_t>(0x5B53A0, 0x5B5660, 0x5BA2D0, 0x659850, 0x6598A0, 0x658800);
+WRAPPER RwBool RwD3D8SetTexture(RwTexture*, RwUInt32) { VARJMP(RwD3D8SetTexture_A); }
+static uint32_t RwD3D8SetRenderState_A = AddressByVersion<uint32_t>(0x5B3CF0, 0x5B3FB0, 0x5B84B0, 0x6582A0, 0x6582F0, 0x657250);
+WRAPPER RwBool RwD3D8SetRenderState(RwUInt32, RwUInt32) { VARJMP(RwD3D8SetRenderState_A); }
+static uint32_t RwD3D8GetRenderState_A = AddressByVersion<uint32_t>(0x5B3D40, 0x5B4000, 0x5B8510, 0x6582F0, 0x658340, 0x6572A0);
+WRAPPER void RwD3D8GetRenderState(RwUInt32, void*) { VARJMP(RwD3D8GetRenderState_A); }
+static uint32_t RwD3D8SetTextureStageState_A = AddressByVersion<uint32_t>(0x5B3D60, 0x5B4020, 0x5B8530, 0x658310, 0x658360, 0x6572C0);
+WRAPPER RwBool RwD3D8SetTextureStageState(RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8SetTextureStageState_A); }
+static uint32_t RwD3D8SetVertexShader_A = AddressByVersion<uint32_t>(0x5BAF90, 0x5BB250, 0x5BF440, 0x65F2F0, 0x65F340, 0x65E2A0);
+WRAPPER RwBool RwD3D8SetVertexShader(RwUInt32) { VARJMP(RwD3D8SetVertexShader_A); }
+static uint32_t RwD3D8SetPixelShader_A = AddressByVersion<uint32_t>(0x5BAFD0, 0x5BB290, 0x5BF4A0, 0x65F330, 0x65F380, 0x65E2E0);
+WRAPPER RwBool RwD3D8SetPixelShader(RwUInt32 handle) { VARJMP(RwD3D8SetPixelShader_A); }
+static uint32_t RwD3D8SetStreamSource_A = AddressByVersion<uint32_t>(0x5BB010, 0x5BB2D0, 0x5BF500, 0x65F370, 0x65F3C0, 0x65E320);
+WRAPPER RwBool RwD3D8SetStreamSource(RwUInt32, void*, RwUInt32) { VARJMP(RwD3D8SetStreamSource_A); }
+static uint32_t RwD3D8SetIndices_A = AddressByVersion<uint32_t>(0x5BB060, 0x5BB320, 0x5BF590, 0x65F3C0, 0x65F410, 0x65E370);
+WRAPPER RwBool RwD3D8SetIndices(void*, RwUInt32) { VARJMP(RwD3D8SetIndices_A); }
+static uint32_t RwD3D8DrawIndexedPrimitive_A = AddressByVersion<uint32_t>(0x5BB0B0, 0x5BB370, 0x5BF610, 0x65F410, 0x65F460, 0x65E3C0);
+WRAPPER RwBool RwD3D8DrawIndexedPrimitive(RwUInt32, RwUInt32, RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8DrawIndexedPrimitive_A); }
+static uint32_t RwD3D8DrawPrimitive_A = AddressByVersion<uint32_t>(0x5BB140, 0x5BB400, 0x5BF6C0, 0x65F4A0, 0x65F4F0, 0x65E450);
+WRAPPER RwBool RwD3D8DrawPrimitive(RwUInt32, RwUInt32, RwUInt32) { VARJMP(RwD3D8DrawPrimitive_A); }
+static uint32_t RwD3D8SetSurfaceProperties_A = AddressByVersion<uint32_t>(0x5BB490, 0x5BB750, 0x5BFB30, 0x65F7F0, 0x65F840, 0x65E7A0);
+WRAPPER RwBool RwD3D8SetSurfaceProperties(const RwRGBA*, const RwSurfaceProperties*, RwBool) { VARJMP(RwD3D8SetSurfaceProperties_A); }
+static uint32_t RwD3D8SetTransform_A = AddressByVersion<uint32_t>(0x5BB1D0, 0x5BB490, 0x5BF768, 0x65F530, 0x65F580, 0x65E4E0);
+WRAPPER RwBool RwD3D8SetTransform(RwUInt32 a1, const void* a2)
+{
+ if (gtaversion != III_STEAM)
+ VARJMP(RwD3D8SetTransform_A);
+
+ __asm
+ {
+ mov edx, [esp+8]
+ mov eax, [esp+4]
+ jmp RwD3D8SetTransform_A
+ }
+}
+static uint32_t RwD3D8GetTransform_A = AddressByVersion<uint32_t>(0x5BB310, 0x5BB5D0, 0x5BF930, 0x65F670, 0x65F6C0, 0x65E620);
+WRAPPER void RwD3D8GetTransform(RwUInt32, void*) { VARJMP(RwD3D8GetTransform_A); }
+
+//WRAPPER RwBool RwD3D8SetLight(RwInt32, const void*) { EAXJMP(0x65FB20); }
+//WRAPPER RwBool RwD3D8EnableLight(RwInt32, RwBool) { EAXJMP(0x65FC10); }
+
+static uint32_t rwD3D8RenderStateIsVertexAlphaEnable_A = AddressByVersion<uint32_t>(0x5B5A50, 0x5B5D10, 0x5BA970, 0x659F60, 0x659FB0, 0x658F10);
+WRAPPER RwBool rwD3D8RenderStateIsVertexAlphaEnable(void) { VARJMP(rwD3D8RenderStateIsVertexAlphaEnable_A); };
+static uint32_t rwD3D8RenderStateVertexAlphaEnable_A = AddressByVersion<uint32_t>(0x5B57E0, 0x5B5AA0, 0x5BA840, 0x659CF0, 0x659D40, 0x658CA0);
+WRAPPER void rwD3D8RenderStateVertexAlphaEnable(RwBool x) { VARJMP(rwD3D8RenderStateVertexAlphaEnable_A); };
+static uint32_t RpAtomicGetWorldBoundingSphere_A = AddressByVersion<uint32_t>(0x59E800, 0x59EAC0, 0x59EB20, 0x640710, 0x640760, 0x63F6C0);
+WRAPPER const RwSphere *RpAtomicGetWorldBoundingSphere(RpAtomic*) { VARJMP(RpAtomicGetWorldBoundingSphere_A); };
+static uint32_t RwD3D8CameraIsSphereFullyInsideFrustum_A = AddressByVersion<uint32_t>(0x5BBC40, 0x5BBF00, 0x5C0450, 0x65FFB0, 0x660000, 0x65EF60);
+WRAPPER RwBool RwD3D8CameraIsSphereFullyInsideFrustum(const void*, const void*) { VARJMP(RwD3D8CameraIsSphereFullyInsideFrustum_A); };
+static uint32_t RwD3D8CameraIsBBoxFullyInsideFrustum_A = AddressByVersion<uint32_t>(0x5BBCA0, 0x5BBF60, 0x5C04B0, 0x660010, 0x660060, 0x65EFC0);
+WRAPPER RwBool RwD3D8CameraIsBBoxFullyInsideFrustum(const void*, const void*) { VARJMP(RwD3D8CameraIsBBoxFullyInsideFrustum_A); };
+
+static uint32_t RtBMPImageRead_A = AddressByVersion<uint32_t>(0x5AFE70, 0x5B0130, 0x5B3390, 0x657870, 0x6578C0, 0x656820);
+WRAPPER RwImage *RtBMPImageRead(const RwChar*) { VARJMP(RtBMPImageRead_A); }
+static uint32_t RwImageFindRasterFormat_A = AddressByVersion<uint32_t>(0x5BBF80, 0x5BC240, 0x5C0C40, 0x6602E0, 0x660330, 0x65F290);
+WRAPPER RwImage *RwImageFindRasterFormat(RwImage*, RwInt32, RwInt32*, RwInt32*, RwInt32*, RwInt32*) { VARJMP(RwImageFindRasterFormat_A); }
+static uint32_t RwImageDestroy_A = AddressByVersion<uint32_t>(0x5A9180, 0x5A9440, 0x5AB6A0, 0x6512B0, 0x651300, 0x650260);
+WRAPPER RwBool RwImageDestroy(RwImage*) { VARJMP(RwImageDestroy_A); }
+
+// ADDRESS
+static uint32_t RwImageCreate_A = AddressByVersion<uint32_t>(0x5A9120, 0, 0, 0x651250, 0, 0);
+WRAPPER RwImage *RwImageCreate(RwInt32, RwInt32, RwInt32) { VARJMP(RwImageCreate_A); }
+static uint32_t RwImageAllocatePixels_A = AddressByVersion<uint32_t>(0x5A91E0, 0, 0, 0x651310, 0, 0);
+WRAPPER RwImage *RwImageAllocatePixels(RwImage *) { VARJMP(RwImageAllocatePixels_A); }
+static uint32_t RwStreamOpen_A = AddressByVersion<uint32_t>(0x5A3FE0, 0, 0, 0x6459C0, 0, 0);
+WRAPPER RwStream *RwStreamOpen(RwStreamType, RwStreamAccessType, const void*) { VARJMP(RwStreamOpen_A); }
+static uint32_t RwStreamClose_A = AddressByVersion<uint32_t>(0x5A3F10, 0, 0, 0x6458F0, 0, 0);
+WRAPPER RwBool RwStreamClose(RwStream*, void*) { VARJMP(RwStreamClose_A); }
+static uint32_t RwStreamRead_A = AddressByVersion<uint32_t>(0x5A3AD0, 0, 0, 0x6454B0, 0, 0);
+WRAPPER RwUInt32 RwStreamRead(RwStream*, void*, RwUInt32) { VARJMP(RwStreamRead_A); }
+static uint32_t RwStreamSkip_A = AddressByVersion<uint32_t>(0x5A3DF0, 0, 0, 0x6457D0, 0, 0);
+WRAPPER RwStream *RwStreamSkip(RwStream*, RwUInt32) { VARJMP(RwStreamSkip_A); }
+static uint32_t RwStreamFindChunk_A = AddressByVersion<uint32_t>(0x5AA540, 0, 0, 0x64FAC0, 0, 0);
+WRAPPER RwBool RwStreamFindChunk(RwStream*, RwUInt32, RwUInt32*, RwUInt32*) { VARJMP(RwStreamFindChunk_A); }
+static uint32_t RwTexDictionaryStreamRead_A = AddressByVersion<uint32_t>(0x5924A0, 0, 0, 0x61E710, 0, 0);
+WRAPPER RwTexDictionary *RwTexDictionaryStreamRead(RwStream*) { VARJMP(RwTexDictionaryStreamRead_A); }
+
+static uint32_t RpGeometryLock_A = AddressByVersion<uint32_t>(0, 0, 0, 0x64CCD0, 0, 0);
+static uint32_t RpGeometryUnlock_A = AddressByVersion<uint32_t>(0, 0, 0, 0x64CD00, 0, 0);
+WRAPPER RpGeometry *RpGeometryLock(RpGeometry*, RwInt32) { VARJMP(RpGeometryLock_A); }
+WRAPPER RpGeometry *RpGeometryUnlock(RpGeometry*) { VARJMP(RpGeometryUnlock_A); }
+WRAPPER RpGeometry *RpGeometryForAllMaterials(RpGeometry*, RpMaterialCallBack, void*) { EAXJMP(0x5ACBF0); }
+
+static uint32_t RwIm2DGetNearScreenZ_A = AddressByVersion<uint32_t>(0x5A43A0, 0x5A4660, 0x5A5340, 0x649B80, 0x649BD0, 0x648B30);
+WRAPPER RwReal RwIm2DGetNearScreenZ(void) { VARJMP(RwIm2DGetNearScreenZ_A); }
+// ADDRESS
+static uint32_t RwIm2DGetFarScreenZ_A = AddressByVersion<uint32_t>(0x5A43B0, 0, 0, 0x649B90, 0, 0);
+WRAPPER RwReal RwIm2DGetFarScreenZ(void) { VARJMP(RwIm2DGetFarScreenZ_A); }
+WRAPPER RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices) { EAXJMP(0x5A4430); }
+static uint32_t RwIm2DRenderIndexedPrimitive_A = AddressByVersion<uint32_t>(0x5A4440, 0x5A4700, 0x5A5440, 0x649C20, 0x649C70, 0x648BD0);
+WRAPPER RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType, RwIm2DVertex*, RwInt32, RwImVertexIndex*, RwInt32) { VARJMP(RwIm2DRenderIndexedPrimitive_A); }
+
+WRAPPER RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices) { EAXJMP(0x5B6820); }
+WRAPPER void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags) { EAXJMP(0x5B6720); }
+WRAPPER RwBool RwIm3DEnd(void) { EAXJMP(0x5B67F0); }
+
+static uint32_t _rwObjectHasFrameSetFrame_A = AddressByVersion<uint32_t>(0x5BC950, 0x5BCC10, 0x5C1820, 0x660CC0, 0x660D10, 0x65FC70);
+WRAPPER void _rwObjectHasFrameSetFrame(void*, RwFrame*) { VARJMP(_rwObjectHasFrameSetFrame_A); }
+
+static uint32_t RxPipelineCreate_A = AddressByVersion<uint32_t>(0x5C2E00, 0x5C30C0, 0x5C8FC0, 0x668FC0, 0x669010, 0x667F70);
+WRAPPER RxPipeline *RxPipelineCreate(void) { VARJMP(RxPipelineCreate_A); }
+static uint32_t RxPipelineLock_A = AddressByVersion<uint32_t>(0x5D29F0, 0x5D2CB0, 0x5DDBF0, 0x67AC50, 0x67ACA0, 0x679C00);
+WRAPPER RxLockedPipe *RxPipelineLock(RxPipeline*) { VARJMP(RxPipelineLock_A); }
+static uint32_t RxLockedPipeUnlock_A = AddressByVersion<uint32_t>(0x5D1FA0, 0x5D2260, 0x5DD0C0, 0x67A200, 0x67A250, 0x6791B0);
+WRAPPER RxPipeline *RxLockedPipeUnlock(RxLockedPipe*) { VARJMP(RxLockedPipeUnlock_A); }
+static uint32_t RxNodeDefinitionGetD3D8AtomicAllInOne_A = AddressByVersion<uint32_t>(0x5DC500, 0x5DC7C0, 0x5EC470, 0x67CB90, 0x67CBE0, 0x67BB40);
+WRAPPER RxNodeDefinition *RxNodeDefinitionGetD3D8AtomicAllInOne(void) { VARJMP(RxNodeDefinitionGetD3D8AtomicAllInOne_A); }
+static uint32_t RxLockedPipeAddFragment_A = AddressByVersion<uint32_t>(0x5D2BA0, 0x5D2E60, 0x5DE000, 0x67AE00, 0x67AE50, 0x679DB0);
+WRAPPER RxLockedPipe *RxLockedPipeAddFragment(RxLockedPipe*, RwUInt32*, RxNodeDefinition*, ...) { VARJMP(RxLockedPipeAddFragment_A); }
+static uint32_t _rxPipelineDestroy_A = AddressByVersion<uint32_t>(0x5C2E70, 0x5C3130, 0x5C9030, 0x669030, 0x669080, 0x667FE0);
+WRAPPER void _rxPipelineDestroy(RxPipeline*) { VARJMP(_rxPipelineDestroy_A); }
+static uint32_t RxPipelineFindNodeByName_A = AddressByVersion<uint32_t>(0x5D2B10, 0x5D2DD0, 0x5DDF40, 0x67AD70, 0x67ADC0, 0x679D20);
+WRAPPER RxPipelineNode *RxPipelineFindNodeByName(RxPipeline*, const RwChar*, RxPipelineNode*, RwInt32*) { VARJMP(RxPipelineFindNodeByName_A); }
+static uint32_t RxD3D8AllInOneSetRenderCallBack_A = AddressByVersion<uint32_t>(0x5DFC60, 0x5DFF20, 0x5EE330, 0x678E30, 0x678E80, 0x677DE0);
+WRAPPER void RxD3D8AllInOneSetRenderCallBack(RxPipelineNode*, RxD3D8AllInOneRenderCallBack) { VARJMP(RxD3D8AllInOneSetRenderCallBack_A); }
+RxD3D8AllInOneRenderCallBack RxD3D8AllInOneGetRenderCallBack(RxPipelineNode *node)
+{
+ return *(RxD3D8AllInOneRenderCallBack*)((uint8*)node->privateData + 12);
+}
+void RxD3D8AllInOneSetInstanceCallBack(RxPipelineNode *node, RxD3D8AllInOneInstanceCallBack callback)
+{
+ *(RxD3D8AllInOneInstanceCallBack*)node->privateData = callback;
+}
+
+static uint32_t rxD3D8DefaultRenderCallback_A = AddressByVersion<uint32_t>(0x5DF960, 0x5DFC20, 0x5EE350, 0x678B30, 0x678B80, 0x677AE0);
+WRAPPER void rxD3D8DefaultRenderCallback(RwResEntry*, void*, RwUInt8, RwUInt32) { VARJMP(rxD3D8DefaultRenderCallback_A); }
+static uint32_t rwD3D8AtomicMatFXRenderCallback_A = AddressByVersion<uint32_t>(0x5D0B80, 0x5D0E40, 0x5D8B20, 0x676460, 0x6764B0, 0x675410);
+
+WRAPPER RpMaterial *RpMatFXMaterialSetEffects(RpMaterial*, RpMatFXMaterialFlags) { EAXJMP(0x5B3780); }
+WRAPPER RpMaterial *RpMatFXMaterialSetupEnvMap(RpMaterial*, RwTexture*, RwFrame*, RwBool, RwReal) { EAXJMP(0x5B38D0); }
+WRAPPER void rwD3D8AtomicMatFXRenderCallback(RwResEntry*, void*, RwUInt8, RwUInt32) { VARJMP(rwD3D8AtomicMatFXRenderCallback_A); }
+WRAPPER RpAtomic *RpMatFXAtomicEnableEffects(RpAtomic*) { EAXJMP(0x5B3750); }
+
+WRAPPER RpAtomic *RpAtomicSetFrame(RpAtomic*, RwFrame*) { EAXJMP(0x5A0600); }
+WRAPPER RwBool RpAtomicDestroy(RpAtomic*) { EAXJMP(0x59F020); }
+WRAPPER RpAtomic *RpAtomicClone(RpAtomic*) { EAXJMP(0x59F0A0); }
+WRAPPER RpAtomic *RpAtomicSetGeometry(RpAtomic*, RpGeometry*, RwUInt32) { EAXJMP(0x59EFA0); }
+WRAPPER RwInt32 RpAtomicRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A0510); }
+WRAPPER void _rpAtomicResyncInterpolatedSphere(RpAtomic*) { EAXJMP(0x59E6C0); }
+
+WRAPPER RpAtomic *AtomicDefaultRenderCallBack(RpAtomic*) { EAXJMP(0x59E690); }
+
+static uint32_t RpClumpForAllAtomics_A = AddressByVersion<uint32_t>(0x59EDD0, 0x59F090, 0x59EFC0, 0x640D00, 0x640D50, 0x63FCB0);
+WRAPPER RpClump *RpClumpForAllAtomics(RpClump*, RpAtomicCallBack, void*) { VARJMP(RpClumpForAllAtomics_A); }
+WRAPPER RpClump *RpClumpRender(RpClump *) { EAXJMP(0x59ED80); }
+WRAPPER RpClump *RpClumpClone(RpClump *) { EAXJMP(0x59F1B0); }
+WRAPPER RwBool RpClumpDestroy(RpClump * clump) { EAXJMP(0x59F500); }
+WRAPPER RwInt32 RpClumpRegisterPlugin(RwInt32, RwUInt32, RwPluginObjectConstructor, RwPluginObjectDestructor, RwPluginObjectCopy) { EAXJMP(0x5A0540); }
+WRAPPER RpClump *RpClumpRemoveAtomic(RpClump*, RpAtomic*) { EAXJMP(0x59F6B0); }
+WRAPPER RpClump *RpClumpAddAtomic(RpClump*, RpAtomic*) { EAXJMP(0x59F680); }
+
+WRAPPER RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color) { EAXJMP(0x5BC320); } \ No newline at end of file
diff --git a/src/templates.h b/src/templates.h
new file mode 100644
index 00000000..75bfd26b
--- /dev/null
+++ b/src/templates.h
@@ -0,0 +1,179 @@
+#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);
+ 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;
+ }
+};
+
+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]);
+ }
+ // Shutdown
+ 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;
+ }
+};