diff options
Diffstat (limited to 'src/core')
87 files changed, 24231 insertions, 0 deletions
diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp new file mode 100644 index 00000000..58e65d24 --- /dev/null +++ b/src/core/Camera.cpp @@ -0,0 +1,1310 @@ +#include "common.h" +#include "patcher.h" +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Pad.h" +#include "General.h" +#include "ZoneCull.h" +#include "SurfaceTable.h" +#include "MBlur.h" +#include "Camera.h" + +const float DefaultFOV = 70.0f; // beta: 80.0f + +CCamera &TheCamera = *(CCamera*)0x6FACF8; +bool &CCamera::m_bUseMouse3rdPerson = *(bool *)0x5F03D8; + +WRAPPER void CCamera::DrawBordersForWideScreen(void) { EAXJMP(0x46B430); } +WRAPPER void CCamera::CalculateDerivedValues(void) { EAXJMP(0x46EEA0); } +WRAPPER void CCamera::Restore(void) { EAXJMP(0x46F990); } +WRAPPER void CCamera::SetWidescreenOff(void) { EAXJMP(0x46FF10); } + +bool +CCamera::IsSphereVisible(const CVector ¢er, float radius, const CMatrix *mat) +{ + RwV3d c; + c = *(RwV3d*)¢er; + 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 ¢er, const CMatrix *mat) +{ + RwV3d c; + c = *(RwV3d*)¢er; + RwV3dTransformPoints(&c, &c, 1, &mat->m_matrix); + if(c.y < CDraw::GetNearClipZ()) return false; + if(c.y > CDraw::GetFarClipZ()) return false; + if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > 0.0f) return false; + if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > 0.0f) return false; + if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > 0.0f) return false; + if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > 0.0f) return false; + return true; +} + +bool +CCamera::IsBoxVisible(RwV3d *box, const CMatrix *mat) +{ + int i; + int frustumTests[6] = { 0 }; + RwV3dTransformPoints(box, box, 8, &mat->m_matrix); + + for(i = 0; i < 8; i++){ + if(box[i].y < CDraw::GetNearClipZ()) frustumTests[0]++; + if(box[i].y > CDraw::GetFarClipZ()) frustumTests[1]++; + if(box[i].x*m_vecFrustumNormals[0].x + box[i].y*m_vecFrustumNormals[0].y > 0.0f) frustumTests[2]++; + if(box[i].x*m_vecFrustumNormals[1].x + box[i].y*m_vecFrustumNormals[1].y > 0.0f) frustumTests[3]++; +// Why not test z? +// if(box[i].y*m_vecFrustumNormals[2].y + box[i].z*m_vecFrustumNormals[2].z > 0.0f) frustumTests[4]++; +// if(box[i].y*m_vecFrustumNormals[3].y + box[i].z*m_vecFrustumNormals[3].z > 0.0f) frustumTests[5]++; + } + for(i = 0; i < 6; i++) + if(frustumTests[i] == 8) + return false; // Box is completely outside of one plane + return true; +} + +int +CCamera::GetLookDirection(void) +{ + if(Cams[ActiveCam].Mode == CCam::MODE_CAMONASTRING || + Cams[ActiveCam].Mode == CCam::MODE_FIRSTPERSON || + Cams[ActiveCam].Mode == CCam::MODE_BEHINDBOAT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED) + return Cams[ActiveCam].DirectionWasLooking; + return LOOKING_FORWARD;; +} + +WRAPPER void CCamera::Fade(float timeout, int16 direction) { EAXJMP(0x46B3A0); } +WRAPPER void CCamera::ProcessFade(void) { EAXJMP(0x46F080); } +WRAPPER void CCamera::ProcessMusicFade(void) { EAXJMP(0x46F1E0); } + +int +CCamera::GetScreenFadeStatus(void) +{ + if(m_fFLOATingFade == 0.0f) + return FADE_0; + if(m_fFLOATingFade == 255.0f) + return FADE_2; + return FADE_1; +} + +void +CCamera::SetFadeColour(uint8 r, uint8 g, uint8 b) +{ + m_FadeTargetIsSplashScreen = r == 0 && g == 0 && b == 0; + CDraw::FadeRed = r; + CDraw::FadeGreen = g; + CDraw::FadeBlue = b; +} + +void +CCamera::SetMotionBlur(int r, int g, int b, int a, int type) +{ + m_BlurRed = r; + m_BlurGreen = g; + m_BlurBlue = b; + m_motionBlur = a; + m_BlurType = type; +} + +void +CCamera::SetMotionBlurAlpha(int a) +{ + m_imotionBlurAddAlpha = a; +} + +void +CCamera::RenderMotionBlur(void) +{ + if(m_BlurType == 0) + return; + + CMBlur::MotionBlurRender(m_pRwCamera, + m_BlurRed, m_BlurGreen, m_BlurBlue, + m_motionBlur, m_BlurType, m_imotionBlurAddAlpha); +} + +void +CCamera::ClearPlayerWeaponMode() +{ + PlayerWeaponMode.Mode = 0; + PlayerWeaponMode.MaxZoom = 1; + PlayerWeaponMode.MinZoom = -1; + PlayerWeaponMode.Duration = 0.0f; +} + + +/* + * + * CCam + * + */ + + +// MaxSpeed is a limit of how fast the value is allowed to change. 1.0 = to Target in up to 1ms +// Acceleration is how fast the speed will change to MaxSpeed. 1.0 = to MaxSpeed in 1ms +void +WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle) +{ + float Delta = Target - *CurrentValue; + + if(IsAngle){ + while(Delta >= PI) Delta -= 2*PI; + while(Delta < -PI) Delta += 2*PI; + } + + float TargetSpeed = Delta * MaxSpeed; + // Add or subtract absolute depending on sign, genius! +// if(TargetSpeed - *CurrentSpeed > 0.0f) +// *CurrentSpeed += Acceleration * fabs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); +// else +// *CurrentSpeed -= Acceleration * fabs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); + // this is simpler: + *CurrentSpeed += Acceleration * (TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); + + // Clamp speed if we overshot + if(TargetSpeed < 0.0f && *CurrentSpeed < TargetSpeed) + *CurrentSpeed = TargetSpeed; + else if(TargetSpeed > 0.0f && *CurrentSpeed > TargetSpeed) + *CurrentSpeed = TargetSpeed; + + *CurrentValue += *CurrentSpeed * min(10.0f, CTimer::GetTimeStep()); +} + +void +CCam::GetVectorsReadyForRW(void) +{ + CVector right; + Up = CVector(0.0f, 0.0f, 1.0f); + Front.Normalise(); + if(Front.x == 0.0f && Front.y == 0.0f){ + Front.x = 0.0001f; + Front.y = 0.0001f; + } + right = CrossProduct(Front, Up); + right.Normalise(); + Up = CrossProduct(right, Front); +} + +// This code is really bad. wtf R*? +CVector +CCam::DoAverageOnVector(const CVector &vec) +{ + int i; + CVector Average = { 0.0f, 0.0f, 0.0f }; + + if(ResetStatics){ + m_iRunningVectorArrayPos = 0; + m_iRunningVectorCounter = 1; + } + + // TODO: make this work with NUMBER_OF_VECTORS_FOR_AVERAGE != 2 + if(m_iRunningVectorCounter == 3){ + m_arrPreviousVectors[0] = m_arrPreviousVectors[1]; + m_arrPreviousVectors[1] = vec; + }else + m_arrPreviousVectors[m_iRunningVectorArrayPos] = vec; + + for(i = 0; i <= m_iRunningVectorArrayPos; i++) + Average += m_arrPreviousVectors[i]; + Average /= i; + + m_iRunningVectorArrayPos++; + m_iRunningVectorCounter++; + if(m_iRunningVectorArrayPos >= NUMBER_OF_VECTORS_FOR_AVERAGE) + m_iRunningVectorArrayPos = NUMBER_OF_VECTORS_FOR_AVERAGE-1; + if(m_iRunningVectorCounter > NUMBER_OF_VECTORS_FOR_AVERAGE+1) + m_iRunningVectorCounter = NUMBER_OF_VECTORS_FOR_AVERAGE+1; + + return Average; +} + +// Rotate Beta in direction opposite of BetaOffset in 5 deg. steps. +// Return the first angle for which Beta + BetaOffset + Angle has a clear view. +// i.e. BetaOffset is a safe zone so that Beta + Angle is really clear. +// If BetaOffset == 0, try both directions. +float +CCam::GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies) +{ + CColPoint point; + CEntity *ent = nil; + CVector ToSource; + float a; + + // This would be so much nicer if we just got the step variable before the loop...R* + + for(a = 0.0f; a <= PI; a += DEGTORAD(5.0f)){ + if(BetaOffset <= 0.0f){ + ToSource = CVector(cos(Beta + BetaOffset + a), sin(Beta + BetaOffset + a), 0.0f)*Dist; + if(!CWorld::ProcessLineOfSight(Target, Target + ToSource, + point, ent, checkBuildings, checkVehicles, checkPeds, + checkObjects, checkDummies, true, true)) + return a; + } + if(BetaOffset >= 0.0f){ + ToSource = CVector(cos(Beta + BetaOffset - a), sin(Beta + BetaOffset - a), 0.0f)*Dist; + if(!CWorld::ProcessLineOfSight(Target, Target + ToSource, + point, ent, checkBuildings, checkVehicles, checkPeds, + checkObjects, checkDummies, true, true)) + return -a; + } + } + return 0.0f; +} + +static float DefaultAcceleration = 0.045f; +static float DefaultMaxStep = 0.15f; + +void +CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + const float GroundDist = 1.85f; + + CVector TargetCoors, Dist, IdealSource; + float Length = 0.0f; + float LateralLeft = 0.0f; + float LateralRight = 0.0f; + float Center = 0.0f; + static bool PreviouslyObscured; + static bool PickedASide; + static float FixedTargetOrientation = 0.0f; + float AngleToGoTo = 0.0f; + float BetaOffsetAvoidBuildings = 0.45f; // ~25 deg + float BetaOffsetGoingBehind = 0.45f; + bool GoingBehind = false; + bool Obscured = false; + bool BuildingCheckObscured = false; + bool HackPlayerOnStoppingTrain = false; + static int TimeIndicatedWantedToGoDown = 0; + static bool StartedCountingForGoDown = false; + float DeltaBeta; + + m_bFixingBeta = false; + bBelowMinDist = false; + bBehindPlayerDesired = false; + + assert(CamTargetEntity->IsPed()); + + // CenterDist should be > LateralDist because we don't have an angle for safety in this case + float CenterDist, LateralDist; + float AngleToGoToSpeed; + if(m_fCloseInPedHeightOffsetSpeed > 0.00001f){ + LateralDist = 0.55f; + CenterDist = 1.25f; + BetaOffsetAvoidBuildings = 0.9f; // ~50 deg + BetaOffsetGoingBehind = 0.9f; + AngleToGoToSpeed = 0.88254666f; + }else{ + LateralDist = 0.8f; + CenterDist = 1.35f; + if(TheCamera.PedZoomIndicator == 1.0f || TheCamera.PedZoomIndicator == 4.0f){ + LateralDist = 1.25f; + CenterDist = 1.6f; + } + AngleToGoToSpeed = 0.43254671f; + } + + FOV = DefaultFOV; + + if(ResetStatics){ + Rotating = false; + m_bCollisionChecksOn = true; + FixedTargetOrientation = 0.0f; + PreviouslyObscured = false; + PickedASide = false; + StartedCountingForGoDown = false; + AngleToGoTo = 0.0f; + // unused LastAngleWithNoPickedASide + } + + + TargetCoors = CameraTarget; + IdealSource = Source; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + + CVector TempTargetCoors; + TempTargetCoors = DoAverageOnVector(TargetCoors); + TargetCoors = TempTargetCoors; + // Add this unknown offset, but later it's removed again + TargetCoors.z += m_fUnknownZOffSet; + + Dist.x = IdealSource.x - TargetCoors.x; + Dist.y = IdealSource.y - TargetCoors.y; + Length = Dist.Magnitude2D(); + + // Cam on a string. With a fixed distance. Zoom in/out is done later. + if(Length != 0.0f) + IdealSource = TargetCoors + CVector(Dist.x, Dist.y, 0.0f)/Length * GroundDist; + else + IdealSource = TargetCoors + CVector(1.0f, 1.0f, 0.0f); + + // TODO: what's transition beta? + if(TheCamera.m_bUseTransitionBeta && ResetStatics){ + CVector VecDistance; + IdealSource.x = TargetCoors.x + GroundDist*cos(m_fTransitionBeta); + IdealSource.y = TargetCoors.y + GroundDist*sin(m_fTransitionBeta); + Beta = CGeneral::GetATanOfXY(IdealSource.x - TargetCoors.x, IdealSource.y - TargetCoors.y); + }else + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + + if(TheCamera.m_bCamDirectlyBehind){ + m_bCollisionChecksOn = true; + Beta = TargetOrientation + PI; + } + + if(FindPlayerVehicle()) + if(FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_TRAIN) + HackPlayerOnStoppingTrain = true; + + if(TheCamera.m_bCamDirectlyInFront){ + m_bCollisionChecksOn = true; + Beta = TargetOrientation; + } + + while(Beta >= PI) Beta -= 2.0f * PI; + while(Beta < -PI) Beta += 2.0f * PI; + + // BUG? is this ever used? + // The values seem to be roughly m_fPedZoomValueSmooth + 1.85 + if(ResetStatics){ + if(TheCamera.PedZoomIndicator == 1.0) m_fRealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == 2.0) m_fRealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == 3.0) m_fRealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == 4.0) m_fRealGroundDist = 2.090556f; + } + // And what is this? It's only used for collision and rotation it seems + float RealGroundDist; + if(TheCamera.PedZoomIndicator == 1.0) RealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == 2.0) RealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == 3.0) RealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == 4.0) RealGroundDist = 2.090556f; + if(m_fCloseInPedHeightOffset > 0.00001f) + RealGroundDist = 1.7016; + + + bool Shooting = false; + CPed *ped = (CPed*)CamTargetEntity; + if(ped->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + if(CPad::GetPad(0)->GetWeapon()) + Shooting = true; + if(ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR || + ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) + Shooting = false; + + + if(m_fCloseInPedHeightOffset > 0.00001f) + TargetCoors.z -= m_fUnknownZOffSet; + + // Figure out if and where we want to rotate + + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + + // Center cam behind player + + GoingBehind = true; + m_bCollisionChecksOn = true; + float OriginalBeta = Beta; + // Set Beta behind player + Beta = TargetOrientation + PI; + TargetCoors.z -= 0.1f; + + AngleToGoTo = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false); + if(AngleToGoTo != 0.0f){ + if(AngleToGoTo < 0.0f) + AngleToGoTo -= AngleToGoToSpeed; + else + AngleToGoTo += AngleToGoToSpeed; + }else{ + float LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetGoingBehind, true, false, false, true, false); + float LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetGoingBehind, true, false, false, true, false); + if(LateralLeft == 0.0f && LateralRight != 0.0f) + AngleToGoTo += LateralRight; + else if(LateralLeft != 0.0f && LateralRight == 0.0f) + AngleToGoTo += LateralLeft; + } + + TargetCoors.z += 0.1f; + Beta = OriginalBeta; + + if(PickedASide){ + if(AngleToGoTo == 0.0f) + FixedTargetOrientation = TargetOrientation + PI; + Rotating = true; + }else{ + FixedTargetOrientation = TargetOrientation + PI + AngleToGoTo; + Rotating = true; + PickedASide = true; + } + }else{ + + // Rotate cam to avoid clipping into buildings + + TargetCoors.z -= 0.1f; + + Center = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false); + if(m_bCollisionChecksOn || PreviouslyObscured || Center != 0.0f || m_fCloseInPedHeightOffset > 0.00001f){ + if(Center != 0.0f){ + AngleToGoTo = Center; + }else{ + LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetAvoidBuildings, true, false, false, true, false); + LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetAvoidBuildings, true, false, false, true, false); + if(LateralLeft == 0.0f && LateralRight != 0.0f){ + AngleToGoTo += LateralRight; + if(m_fCloseInPedHeightOffset > 0.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.7f); + }else if(LateralLeft != 0.0f && LateralRight == 0.0f){ + AngleToGoTo += LateralLeft; + if(m_fCloseInPedHeightOffset > 0.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.7f); + } + } + if(LateralLeft != 0.0f || LateralRight != 0.0f || Center != 0.0f) + BuildingCheckObscured = true; + } + + TargetCoors.z += 0.1f; + } + + if(m_fCloseInPedHeightOffset > 0.00001f) + TargetCoors.z += m_fUnknownZOffSet; + + + // Have to fix to avoid collision + + if(AngleToGoTo != 0.0f){ + Obscured = true; + Rotating = true; + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + if(!PickedASide) + FixedTargetOrientation = Beta + AngleToGoTo; // can this even happen? + }else + FixedTargetOrientation = Beta + AngleToGoTo; + + // This calculation is only really used to figure out how fast to rotate out of collision + + m_fAmountFractionObscured = 1.0f; + CVector PlayerPos = FindPlayerPed()->GetPosition(); + float RotationDist = (AngleToGoTo == Center ? CenterDist : LateralDist) * RealGroundDist; + // What's going on here? - AngleToGoTo? + CVector RotatedSource = PlayerPos + CVector(cos(Beta - AngleToGoTo), sin(Beta - AngleToGoTo), 0.0f) * RotationDist; + + CColPoint colpoint; + CEntity *entity; + if(CWorld::ProcessLineOfSight(PlayerPos, RotatedSource, colpoint, entity, true, false, false, true, false, false, false)){ + if((PlayerPos - RotatedSource).Magnitude() != 0.0f) + m_fAmountFractionObscured = (PlayerPos - colpoint.point).Magnitude() / (PlayerPos - RotatedSource).Magnitude(); + else + m_fAmountFractionObscured = 1.0f; + } + } + if(m_fAmountFractionObscured < 0.0f) m_fAmountFractionObscured = 0.0f; + if(m_fAmountFractionObscured > 1.0f) m_fAmountFractionObscured = 1.0f; + + + + // Figure out speed values for Beta rotation + + float Acceleration, MaxSpeed; + static float AccelerationMult = 0.35f; + static float MaxSpeedMult = 0.85f; + static float AccelerationMultClose = 0.7f; + static float MaxSpeedMultClose = 1.6f; + float BaseAcceleration = 0.025f; + float BaseMaxSpeed = 0.09f; + if(m_fCloseInPedHeightOffset > 0.00001f){ + if(AngleToGoTo == 0.0f){ + BaseAcceleration = 0.022f; + BaseMaxSpeed = 0.04f; + }else{ + BaseAcceleration = DefaultAcceleration; + BaseMaxSpeed = DefaultMaxStep; + } + } + if(AngleToGoTo == 0.0f){ + Acceleration = BaseAcceleration; + MaxSpeed = BaseMaxSpeed; + }else if(CPad::GetPad(0)->ForceCameraBehindPlayer() && !Shooting){ + Acceleration = 0.051f; + MaxSpeed = 0.18f; + }else if(m_fCloseInPedHeightOffset > 0.00001f){ + Acceleration = BaseAcceleration + AccelerationMultClose*sq(m_fAmountFractionObscured - 1.05f); + MaxSpeed = BaseMaxSpeed + MaxSpeedMultClose*sq(m_fAmountFractionObscured - 1.05f); + }else{ + Acceleration = DefaultAcceleration + AccelerationMult*sq(m_fAmountFractionObscured - 1.05f); + MaxSpeed = DefaultMaxStep + MaxSpeedMult*sq(m_fAmountFractionObscured - 1.05f); + } + static float AccelerationLimit = 0.3f; + static float MaxSpeedLimit = 0.65f; + if(Acceleration > AccelerationLimit) Acceleration = AccelerationLimit; + if(MaxSpeed > MaxSpeedLimit) MaxSpeed = MaxSpeedLimit; + + + int MoveState = ((CPed*)CamTargetEntity)->m_nMoveState; + if(MoveState != PEDMOVE_NONE && MoveState != PEDMOVE_STILL && + !CPad::GetPad(0)->ForceCameraBehindPlayer() && !Obscured && !Shooting){ + Rotating = false; + BetaSpeed = 0.0f; + } + + // Now do the Beta rotation + + float Distance = (IdealSource - TargetCoors).Magnitude2D(); + m_fDistanceBeforeChanges = Distance; + + if(Rotating){ + m_bFixingBeta = true; + + while(FixedTargetOrientation >= PI) FixedTargetOrientation -= 2*PI; + while(FixedTargetOrientation < -PI) FixedTargetOrientation += 2*PI; + + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + +/* + // This is inlined WellBufferMe + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + float ReqSpeed = DeltaBeta * MaxSpeed; + // Add or subtract absolute depending on sign, genius! + if(ReqSpeed - BetaSpeed > 0.0f) + BetaSpeed += SpeedStep * fabs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep(); + else + BetaSpeed -= SpeedStep * fabs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep(); + // this would be simpler: + // BetaSpeed += SpeedStep * (ReqSpeed - BetaSpeed) * CTimer::ms_fTimeStep; + + if(ReqSpeed < 0.0f && BetaSpeed < ReqSpeed) + BetaSpeed = ReqSpeed; + else if(ReqSpeed > 0.0f && BetaSpeed > ReqSpeed) + BetaSpeed = ReqSpeed; + + Beta += BetaSpeed * min(10.0f, CTimer::GetTimeStep()); +*/ + WellBufferMe(FixedTargetOrientation, &Beta, &BetaSpeed, MaxSpeed, Acceleration, true); + + if(ResetStatics){ + Beta = FixedTargetOrientation; + BetaSpeed = 0.0f; + } + + Source.x = TargetCoors.x + Distance * cos(Beta); + Source.y = TargetCoors.y + Distance * sin(Beta); + + // Check if we can stop rotating + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(fabs(DeltaBeta) < DEGTORAD(1.0f) && !bBehindPlayerDesired){ + // Stop rotation + PickedASide = false; + Rotating = false; + BetaSpeed = 0.0f; + } + } + + + if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || + HackPlayerOnStoppingTrain || Rotating){ + if(TheCamera.m_bCamDirectlyBehind){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + Distance * cos(Beta); + Source.y = TargetCoors.y + Distance * sin(Beta); + } + if(TheCamera.m_bCamDirectlyInFront){ + Beta = TargetOrientation; + Source.x = TargetCoors.x + Distance * cos(Beta); + Source.y = TargetCoors.y + Distance * sin(Beta); + } + if(HackPlayerOnStoppingTrain){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + Distance * cos(Beta); + Source.y = TargetCoors.y + Distance * sin(Beta); + m_fDimensionOfHighestNearCar = 0.0f; + m_fCamBufferedHeight = 0.0f; + m_fCamBufferedHeightSpeed = 0.0f; + } + // Beta and Source already set in the rotation code + }else{ + Source = IdealSource; + BetaSpeed = 0.0f; + } + + // Subtract m_fUnknownZOffSet from both? + TargetCoors.z -= m_fUnknownZOffSet; + Source.z = IdealSource.z - m_fUnknownZOffSet; + + // Apply zoom now + // m_fPedZoomValueSmooth makes the cam go down the further out it is + // 0.25 -> 0.20 for nearest dist + // 1.50 -> -0.05 for mid dist + // 2.90 -> -0.33 for far dist + Source.z += (2.5f - TheCamera.m_fPedZoomValueSmooth)*0.2f - 0.25f; + // Zoom out camera + Front = TargetCoors - Source; + Front.Normalise(); + Source -= Front * TheCamera.m_fPedZoomValueSmooth; + // and then we move up again + // -0.375 + // 0.25 + // 0.95 + Source.z += (TheCamera.m_fPedZoomValueSmooth - 1.0f)*0.5f + m_fCloseInPedHeightOffset; + + + // Process height offset to avoid peds and cars + + float TargetZOffSet = m_fUnknownZOffSet + m_fDimensionOfHighestNearCar; + TargetZOffSet = max(TargetZOffSet, m_fPedBetweenCameraHeightOffset); + float TargetHeight = CameraTarget.z + TargetZOffSet - Source.z; + + if(TargetHeight > m_fCamBufferedHeight){ + // Have to go up + if(TargetZOffSet == m_fPedBetweenCameraHeightOffset && TargetZOffSet > m_fCamBufferedHeight) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.04f, false); + else if(TargetZOffSet == m_fUnknownZOffSet && TargetZOffSet > m_fCamBufferedHeight){ + // TODO: figure this out + bool foo = false; + switch(((CPhysical*)CamTargetEntity)->m_nLastCollType) + case 2: case 3: case 5: + case 11: case 23: case 26: + foo = true; + if(foo) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.4f, 0.05f, false); + else + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false); + }else + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false); + StartedCountingForGoDown = false; + }else{ + // Have to go down + if(StartedCountingForGoDown){ + if(CTimer::GetTimeInMilliseconds() != TimeIndicatedWantedToGoDown){ + if(TargetHeight > 0.0f) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false); + else + WellBufferMe(0.0f, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false); + } + }else{ + StartedCountingForGoDown = true; + TimeIndicatedWantedToGoDown = CTimer::GetTimeInMilliseconds(); + } + } + + Source.z += m_fCamBufferedHeight; + + + // Clip Source if necessary + + bool ClipSource = m_fCloseInPedHeightOffset > 0.00001f && m_fCamBufferedHeight > 0.001f; + if(GoingBehind || ResetStatics || ClipSource){ + CColPoint colpoint; + CEntity *entity; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colpoint, entity, true, false, false, true, false, true, true)){ + Source = colpoint.point; + if((TargetCoors - Source).Magnitude2D() < 1.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + } + + TargetCoors.z += min(1.0f, m_fCamBufferedHeight/2.0f); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + Front = TargetCoors - Source; + m_fRealGroundDist = Front.Magnitude2D(); + m_fMinDistAwayFromCamWhenInterPolating = m_fRealGroundDist; + Front.Normalise(); + GetVectorsReadyForRW(); + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + PreviouslyObscured = BuildingCheckObscured; + + ResetStatics = false; +} + +void +CCam::Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsVehicle()) + return; + + CVector TargetCoors = CameraTarget; + TargetCoors.z -= 0.2f; + CA_MAX_DISTANCE = 9.95f; + CA_MIN_DISTANCE = 8.5f; + + CVector Dist = Source - TargetCoors; + float Length = Dist.Magnitude2D(); + m_fDistanceBeforeChanges = Length; + if(Length < 0.002f) + Length = 0.002f; + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + if(Length > CA_MAX_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE; + }else if(Length < CA_MIN_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE; + } + TargetCoors.z += 0.8f; + + WorkOutCamHeightWeeCar(TargetCoors, TargetOrientation); + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + FixCamIfObscured(TargetCoors, 1.2f, TargetOrientation); + + Front = TargetCoors - Source; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + ResetStatics = false; + GetVectorsReadyForRW(); +} + +void +CCam::WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation) +{ + CColPoint colpoint; + CEntity *ent; + float TargetZOffSet = 0.0f; + static bool PreviouslyFailedRoadHeightCheck = false; + static float RoadHeightFix = 0.0f; + static float RoadHeightFixSpeed = 0.0f; + + if(ResetStatics){ + RoadHeightFix = 0.0f; + RoadHeightFixSpeed = 0.0f; + Alpha = DEGTORAD(25.0f); + AlphaSpeed = 0.0f; + } + float AlphaTarget = DEGTORAD(25.0f); + if(CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) + AlphaTarget = DEGTORAD(14.0f); + WellBufferMe(AlphaTarget, &Alpha, &AlphaSpeed, 0.1f, 0.05f, true); + Source.z = TargetCoors.z + CA_MAX_DISTANCE*sin(Alpha); + + if(FindPlayerVehicle()){ + m_fUnknownZOffSet = 0.0f; + bool FoundRoad = false; + bool FoundRoof = false; + float RoadZ = 0.0f; + float RoofZ = 0.0f; + + if(CWorld::ProcessVerticalLine(Source, -1000.0f, colpoint, ent, true, false, false, false, false, false, nil) && + ent->IsBuilding()){ + FoundRoad = true; + RoadZ = colpoint.point.z; + } + + if(FoundRoad){ + if(Source.z - RoadZ < 0.9f){ + PreviouslyFailedRoadHeightCheck = true; + TargetZOffSet = RoadZ + 0.9f - Source.z; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + TargetZOffSet = 0.0f; + } + }else{ + if(CWorld::ProcessVerticalLine(Source, 1000.0f, colpoint, ent, true, false, false, false, false, false, nil) && + ent->IsBuilding()){ + FoundRoof = true; + RoofZ = colpoint.point.z; + } + if(FoundRoof){ + if(Source.z - RoofZ < 0.9f){ + PreviouslyFailedRoadHeightCheck = true; + TargetZOffSet = RoofZ + 0.9f - Source.z; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + TargetZOffSet = 0.0f; + } + } + } + } + + if(TargetZOffSet > RoadHeightFix) + RoadHeightFix = TargetZOffSet; + else + WellBufferMe(TargetZOffSet, &RoadHeightFix, &RoadHeightFixSpeed, 0.27f, 0.1f, false); + + if((colpoint.surfaceB == SURFACE_DEFAULT || colpoint.surfaceB >= SURFACE_METAL6) && + colpoint.surfaceB != SURFACE_STEEL && colpoint.surfaceB != SURFACE_STONE && + RoadHeightFix > 1.4f) + RoadHeightFix = 1.4f; + + Source.z += RoadHeightFix; +} + +void +CCam::WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight) +{ + static float LastTargetAlphaWithCollisionOn = 0.0f; + static float LastTopAlphaSpeed = 0.0f; + static float LastAlphaSpeedStep = 0.0f; + static bool PreviousNearCheckNearClipSmall = false; + + bool CamClear = true; + float ModeAlpha = 0.0f; + + if(ResetStatics){ + LastTargetAlphaWithCollisionOn = 0.0f; + LastTopAlphaSpeed = 0.0f; + LastAlphaSpeedStep = 0.0f; + PreviousNearCheckNearClipSmall = false; + } + + float TopAlphaSpeed = 0.15f; + float AlphaSpeedStep = 0.015f; + + float zoomvalue = TheCamera.CarZoomValueSmooth; + if(zoomvalue < 0.1f) + zoomvalue = 0.1f; + if(TheCamera.CarZoomIndicator == 1.0f) + ModeAlpha = CGeneral::GetATanOfXY(23.0f, zoomvalue); // near + else if(TheCamera.CarZoomIndicator == 2.0f) + ModeAlpha = CGeneral::GetATanOfXY(10.8f, zoomvalue); // mid + else if(TheCamera.CarZoomIndicator == 3.0f) + ModeAlpha = CGeneral::GetATanOfXY(7.0f, zoomvalue); // far + + + float Length = (Source - TargetCoors).Magnitude2D(); + if(m_bCollisionChecksOn){ // there's another variable (on PC) but it's uninitialised + CVector Forward = CamTargetEntity->GetForward(); + float CarAlpha = CGeneral::GetATanOfXY(Forward.Magnitude2D(), Forward.z); + // this shouldn't be necessary.... + while(CarAlpha >= PI) CarAlpha -= 2*PI; + while(CarAlpha < -PI) CarAlpha += 2*PI; + + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + float deltaBeta = Beta - TargetOrientation; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + + float BehindCarNess = cos(deltaBeta); // 1 if behind car, 0 if side, -1 if in front + CarAlpha = -CarAlpha * BehindCarNess; + if(CarAlpha < -0.01f) + CarAlpha = -0.01f; + + float DeltaAlpha = CarAlpha - Alpha; + while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI; + while(DeltaAlpha < -PI) DeltaAlpha += 2*PI; + // What's this?? wouldn't it make more sense to clamp? + float AngleLimit = DEGTORAD(1.8f); + if(DeltaAlpha < -AngleLimit) + DeltaAlpha += AngleLimit; + else if(DeltaAlpha > AngleLimit) + DeltaAlpha -= AngleLimit; + else + DeltaAlpha = 0.0f; + + // Now the collision + + float TargetAlpha = 0.0f; + bool FoundRoofCenter = false; + bool FoundRoofSide1 = false; + bool FoundRoofSide2 = false; + bool FoundCamRoof = false; + bool FoundCamGround = false; + float CamRoof = 0.0f; + float CarBottom = TargetCoors.z - TargetHeight/2.0f; + + // Check car center + float CarRoof = CWorld::FindRoofZFor3DCoord(TargetCoors.x, TargetCoors.y, CarBottom, &FoundRoofCenter); + + // Check sides of the car + Forward = CamTargetEntity->GetForward(); // we actually still have that... + Forward.Normalise(); // shouldn't be necessary + float CarSideAngle = CGeneral::GetATanOfXY(Forward.x, Forward.y) + PI/2.0f; + float SideX = 2.5f * cos(CarSideAngle); + float SideY = 2.5f * sin(CarSideAngle); + CWorld::FindRoofZFor3DCoord(TargetCoors.x + SideX, TargetCoors.y + SideY, CarBottom, &FoundRoofSide1); + CWorld::FindRoofZFor3DCoord(TargetCoors.x - SideX, TargetCoors.y - SideY, CarBottom, &FoundRoofSide2); + + // Now find out at what height we'd like to place the camera + float CamGround = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, TargetCoors.z + Length*sin(Alpha + ModeAlpha) + m_fCloseInCarHeightOffset, &FoundCamGround); + float CamTargetZ = 0.0f; + if(FoundCamGround){ + // This is the normal case + CamRoof = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamGround + TargetHeight, &FoundCamRoof); + CamTargetZ = CamGround + TargetHeight*1.5f + 0.1f; + }else{ + FoundCamRoof = false; + CamTargetZ = TargetCoors.z; + } + + if(FoundRoofCenter && !FoundCamRoof && (FoundRoofSide1 || FoundRoofSide2)){ + // Car is under something but camera isn't + // This seems weird... + TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, CarRoof - CamTargetZ - 1.5f); + CamClear = false; + } + if(FoundCamRoof){ + // Camera is under something + float roof = FoundRoofCenter ? min(CamRoof, CarRoof) : CamRoof; + // Same weirdness again? + TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, roof - CamTargetZ - 1.5f); + CamClear = false; + } + while(TargetAlpha >= PI) TargetAlpha -= 2*PI; + while(TargetAlpha < -PI) TargetAlpha += 2*PI; + if(TargetAlpha < DEGTORAD(-7.0f)) + TargetAlpha = DEGTORAD(-7.0f); + + // huh? + if(TargetAlpha > ModeAlpha) + CamClear = true; + // Camera is contrained by collision in some way + PreviousNearCheckNearClipSmall = false; + if(!CamClear){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + + DeltaAlpha = TargetAlpha - (Alpha + ModeAlpha); + while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI; + while(DeltaAlpha < -PI) DeltaAlpha += 2*PI; + + TopAlphaSpeed = 0.3f; + AlphaSpeedStep = 0.03f; + } + + // Now do things if CamClear...but what is that anyway? + float CamZ = TargetCoors.z + Length*sin(Alpha + DeltaAlpha + ModeAlpha) + m_fCloseInCarHeightOffset; + bool FoundGround, FoundRoof; + float CamGround2 = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, CamZ, &FoundGround); + if(FoundGround){ + if(CamClear) + if(CamZ - CamGround2 < 1.5f){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + + float a; + if(Length == 0.0f || CamGround2 + 1.5f - TargetCoors.z == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, CamGround2 + 1.5f - TargetCoors.z); + while(a > PI) a -= 2*PI; + while(a < -PI) a += 2*PI; + DeltaAlpha = a - Alpha; + } + }else{ + if(CamClear){ + float CamRoof2 = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamZ, &FoundRoof); + if(FoundRoof && CamZ - CamRoof2 < 1.5f){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + + if(CamRoof2 > TargetCoors.z + 3.5f) + CamRoof2 = TargetCoors.z + 3.5f; + + float a; + if(Length == 0.0f || CamRoof2 + 1.5f - TargetCoors.z == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, CamRoof2 + 1.5f - TargetCoors.z); + while(a > PI) a -= 2*PI; + while(a < -PI) a += 2*PI; + DeltaAlpha = a - Alpha; + } + } + } + + LastTargetAlphaWithCollisionOn = DeltaAlpha + Alpha; + LastTopAlphaSpeed = TopAlphaSpeed; + LastAlphaSpeedStep = AlphaSpeedStep; + }else{ + if(PreviousNearCheckNearClipSmall) + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + } + + WellBufferMe(LastTargetAlphaWithCollisionOn, &Alpha, &AlphaSpeed, LastTopAlphaSpeed, LastAlphaSpeedStep, true); + + Source.z = TargetCoors.z + sin(Alpha + ModeAlpha)*Length + m_fCloseInCarHeightOffset; +} + +// Rotate cam behind the car when the car is moving forward +bool +CCam::RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation) +{ + bool MovingForward = false; + CPhysical *phys = (CPhysical*)CamTargetEntity; + + float ForwardSpeed = DotProduct(phys->GetForward(), phys->GetSpeed(CVector(0.0f, 0.0f, 0.0f))); + if(ForwardSpeed > 0.02f) + MovingForward = true; + + float Dist = (Source - TargetCoors).Magnitude2D(); + + float DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + if(fabs(DeltaBeta) > DEGTORAD(20.0f) && MovingForward && TheCamera.m_uiTransitionState == 0) + m_bFixingBeta = true; + + CPad *pad = CPad::GetPad(0); + if(!(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight())) + if(DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bCamDirectlyBehind = true; + + if(!m_bFixingBeta && !TheCamera.m_bUseTransitionBeta && !TheCamera.m_bCamDirectlyBehind && !TheCamera.m_bCamDirectlyInFront) + return false; + + bool SetBeta = false; + if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || TheCamera.m_bUseTransitionBeta) + if(&TheCamera.Cams[TheCamera.ActiveCam] == this) + SetBeta = true; + + if(m_bFixingBeta || SetBeta){ + WellBufferMe(TargetOrientation, &Beta, &BetaSpeed, 0.15f, 0.007f, true); + + if(TheCamera.m_bCamDirectlyBehind && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = TargetOrientation; + if(TheCamera.m_bCamDirectlyInFront && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = TargetOrientation + PI; + if(TheCamera.m_bUseTransitionBeta && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = m_fTransitionBeta; + + Source.x = TargetCoors.x - cos(Beta)*Dist; + Source.y = TargetCoors.y - sin(Beta)*Dist; + + // Check if we're done + DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(fabs(DeltaBeta) < DEGTORAD(2.0f)) + m_bFixingBeta = false; + } + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + return true; +} + +// Move the cam to avoid clipping through buildings +bool +CCam::FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation) +{ + CVector Target = TargetCoors; + bool UseEntityPos = false; + CVector EntityPos; + static CColPoint colPoint; + static bool LastObscured = false; + + if(Mode == MODE_BEHINDCAR) + Target.z += TargetHeight/2.0f; + if(Mode == MODE_CAMONASTRING){ + UseEntityPos = true; + Target.z += TargetHeight/2.0f; + EntityPos = CamTargetEntity->GetPosition(); + } + + CVector TempSource = Source; + + bool Obscured1 = false; + bool Obscured2 = false; + bool Fix1 = false; + float Dist1 = 0.0f; + float Dist2 = 0.0f; + CEntity *ent; + if(m_bCollisionChecksOn || LastObscured){ + Obscured1 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true); + if(Obscured1){ + Dist1 = (Target - colPoint.point).Magnitude2D(); + Fix1 = true; + if(UseEntityPos) + Obscured1 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true); + }else if(m_bFixingBeta){ + float d = (TempSource - Target).Magnitude(); + TempSource.x = Target.x - d*cos(TargetOrientation); + TempSource.y = Target.y - d*sin(TargetOrientation); + + // same check again + Obscured2 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true); + if(Obscured2){ + Dist2 = (Target - colPoint.point).Magnitude2D(); + if(UseEntityPos) + Obscured2 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true); + } + } + LastObscured = Obscured1 || Obscured2; + } + + // nothing to do + if(!LastObscured) + return false; + + if(Fix1){ + Source.x = Target.x - cos(Beta)*Dist1; + Source.y = Target.y - sin(Beta)*Dist1; + if(Mode == MODE_BEHINDCAR) + Source = colPoint.point; + }else{ + WellBufferMe(Dist2, &m_fDistanceBeforeChanges, &DistanceSpeed, 0.2f, 0.025f, false); + Source.x = Target.x - cos(Beta)*m_fDistanceBeforeChanges; + Source.y = Target.y - sin(Beta)*m_fDistanceBeforeChanges; + } + + if(ResetStatics){ + m_fDistanceBeforeChanges = (Source - Target).Magnitude2D(); + DistanceSpeed = 0.0f; + Source.x = colPoint.point.x; + Source.y = colPoint.point.y; + } + return true; +} + +void +CCam::Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsVehicle()) + return; + + FOV = DefaultFOV; + + if(ResetStatics){ + AlphaSpeed = 0.0f; + if(TheCamera.m_bIdleOn) + TheCamera.m_uiTimeWeEnteredIdle = CTimer::GetTimeInMilliseconds(); + } + + CBaseModelInfo *mi = CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + CVector Dimensions = mi->GetColModel()->boundingBox.max - mi->GetColModel()->boundingBox.min; + float BaseDist = Dimensions.Magnitude2D(); + + CVector TargetCoors = CameraTarget; + TargetCoors.z += Dimensions.z - 0.1f; // final + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + while(Alpha >= PI) Alpha -= 2*PI; + while(Alpha < -PI) Alpha += 2*PI; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + m_fDistanceBeforeChanges = (Source - TargetCoors).Magnitude2D(); + + Cam_On_A_String_Unobscured(TargetCoors, BaseDist); + WorkOutCamHeight(TargetCoors, TargetOrientation, Dimensions.z); + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + FixCamIfObscured(TargetCoors, Dimensions.z, TargetOrientation); + FixCamWhenObscuredByVehicle(TargetCoors); + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + Front.Normalise(); + GetVectorsReadyForRW(); + ResetStatics = false; +} + +// Basic Cam on a string algorithm +void +CCam::Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist) +{ + CA_MAX_DISTANCE = BaseDist + 0.1f + TheCamera.CarZoomValueSmooth; + CA_MIN_DISTANCE = min(BaseDist*0.6f, 3.5f); + + CVector Dist = Source - TargetCoors; + + if(ResetStatics) + Source = TargetCoors + Dist*(CA_MAX_DISTANCE + 1.0f); + + float Length = Dist.Magnitude2D(); + if(Length < 0.001f){ + // This probably shouldn't happen. reset view + CVector Forward = CamTargetEntity->GetForward(); + Forward.z = 0.0f; + Forward.Normalise(); + Source = TargetCoors - Forward*CA_MAX_DISTANCE; + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + } + + if(Length > CA_MAX_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE; + }else if(Length < CA_MIN_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE; + } +} + +void +CCam::FixCamWhenObscuredByVehicle(const CVector &TargetCoors) +{ + // BUG? is this never reset + static float HeightFixerCarsObscuring = 0.0f; + static float HeightFixerCarsObscuringSpeed = 0.0f; + CColPoint colPoint; + CEntity *entity; + + float HeightTarget = 0.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, false, true, false, false, false, false, false)){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(entity->GetModelIndex()); + HeightTarget = mi->GetColModel()->boundingBox.max.z + 1.0f + TargetCoors.z - Source.z; + if(HeightTarget < 0.0f) + HeightTarget = 0.0f; + } + WellBufferMe(HeightTarget, &HeightFixerCarsObscuring, &HeightFixerCarsObscuringSpeed, 0.2f, 0.025f, false); + Source.z += HeightFixerCarsObscuring; +} + +bool +CCam::Using3rdPersonMouseCam() +{ + return CCamera::m_bUseMouse3rdPerson && + (Mode == MODE_FOLLOWPED || + TheCamera.m_bPlayerIsInGarage && + FindPlayerPed() && FindPlayerPed()->m_nPedState != PED_DRIVING && + Mode != MODE_TOPDOWN1 && this->CamTargetEntity == FindPlayerPed()); +} + +bool +CCam::GetWeaponFirstPersonOn() +{ + CEntity *target = this->CamTargetEntity; + if (target && target->IsPed()) + return ((CPed*)target)->GetWeapon()->m_bAddRotOffset; + + return false; +} + +STARTPATCHES + InjectHook(0x42C760, &CCamera::IsSphereVisible, PATCH_JUMP); + InjectHook(0x46FD00, &CCamera::SetFadeColour, PATCH_JUMP); + + InjectHook(0x46FD40, &CCamera::SetMotionBlur, PATCH_JUMP); + InjectHook(0x46FD80, &CCamera::SetMotionBlurAlpha, PATCH_JUMP); + InjectHook(0x46F940, &CCamera::RenderMotionBlur, PATCH_JUMP); + + InjectHook(0x456F40, WellBufferMe, PATCH_JUMP); + InjectHook(0x4582F0, &CCam::GetVectorsReadyForRW, PATCH_JUMP); + InjectHook(0x457710, &CCam::DoAverageOnVector, PATCH_JUMP); + InjectHook(0x458060, &CCam::GetPedBetaAngleForClearView, PATCH_JUMP); + InjectHook(0x457210, &CCam::Cam_On_A_String_Unobscured, PATCH_JUMP); + InjectHook(0x457A80, &CCam::FixCamWhenObscuredByVehicle, PATCH_JUMP); + InjectHook(0x457B90, &CCam::FixCamIfObscured, PATCH_JUMP); + InjectHook(0x465DA0, &CCam::RotCamIfInFrontCar, PATCH_JUMP); + InjectHook(0x4662D0, &CCam::WorkOutCamHeightWeeCar, PATCH_JUMP); + InjectHook(0x466650, &CCam::WorkOutCamHeight, PATCH_JUMP); + + InjectHook(0x45E3A0, &CCam::Process_FollowPed, PATCH_JUMP); + InjectHook(0x45BE60, &CCam::Process_BehindCar, PATCH_JUMP); + InjectHook(0x45C090, &CCam::Process_Cam_On_A_String, PATCH_JUMP); + + InjectHook(0x473250, &CCamera::dtor, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Camera.h b/src/core/Camera.h new file mode 100644 index 00000000..db5fff46 --- /dev/null +++ b/src/core/Camera.h @@ -0,0 +1,477 @@ +#pragma once +#include "Placeable.h" + +class CEntity; +class CPed; +class CAutomobile; + +#define NUMBER_OF_VECTORS_FOR_AVERAGE 2 + +struct CCam +{ + enum + { + MODE_TOPDOWN1 = 1, + MODE_TOPDOWN2, + MODE_BEHINDCAR, + MODE_FOLLOWPED, + MODE_AIMING, + MODE_DEBUG, + MODE_SNIPER, + MODE_ROCKET, + MODE_MODELVIEW, + MODE_BILL, + MODE_SYPHON, + MODE_CIRCLE, + MODE_CHEESYZOOM, + MODE_WHEELCAM, + MODE_FIXED, + MODE_FIRSTPERSON, + MODE_FLYBY, + MODE_CAMONASTRING, + MODE_REACTIONCAM, + MODE_FOLLOWPEDWITHBINDING, + MODE_CHRISWITHBINDINGPLUSROTATION, + MODE_BEHINDBOAT, + MODE_PLAYERFALLENWATER, + MODE_CAMONTRAINROOF, + MODE_CAMRUNNINGSIDETRAIN, + MODE_BLOODONTHETRACKS, + MODE_IMTHEPASSENGERWOOWOO, + MODE_SYPHONCRIMINFRONT, + MODE_PEDSDEADBABY, + MODE_CUSHYPILLOWSARSE, + MODE_LOOKATCARS, + MODE_ARRESTCAMONE, + MODE_ARRESTCAMTWO, + MODE_M16FIRSTPERSON_34, + MODE_SPECIALFIXEDFORSYPHON, + MODE_FIGHT, + MODE_TOPDOWNPED, + MODE_SNIPER_RUN_AROUND, + MODE_ROCKET_RUN_AROUND, + MODE_FIRSTPERSONPEDONPC_40, + MODE_FIRSTPERSONPEDONPC_41, + MODE_FIRSTPERSONPEDONPC_42, + MODE_EDITOR, + MODE_M16FIRSTPERSON_44 + }; + + bool bBelowMinDist; //used for follow ped mode + bool bBehindPlayerDesired; //used for follow ped mode + bool m_bCamLookingAtVector; + bool m_bCollisionChecksOn; + bool m_bFixingBeta; //used for camera on a string + bool m_bTheHeightFixerVehicleIsATrain; + bool LookBehindCamWasInFront; + bool LookingBehind; + bool LookingLeft; // 32 + bool LookingRight; + bool ResetStatics; //for interpolation type stuff to work + bool Rotating; + + int16 Mode; // CameraMode + uint32 m_uiFinishTime; // 52 + + int m_iDoCollisionChecksOnFrameNum; + int m_iDoCollisionCheckEveryNumOfFrames; + int m_iFrameNumWereAt; // 64 + int m_iRunningVectorArrayPos; + int m_iRunningVectorCounter; + int DirectionWasLooking; + + float f_max_role_angle; //=DEGTORAD(5.0f); + float f_Roll; //used for adding a slight roll to the camera in the + float f_rollSpeed; + float m_fSyphonModeTargetZOffSet; + float m_fUnknownZOffSet; + float m_fAmountFractionObscured; + float m_fAlphaSpeedOverOneFrame; // 100 + float m_fBetaSpeedOverOneFrame; + float m_fBufferedTargetBeta; + float m_fBufferedTargetOrientation; + float m_fBufferedTargetOrientationSpeed; + float m_fCamBufferedHeight; + float m_fCamBufferedHeightSpeed; + float m_fCloseInPedHeightOffset; + float m_fCloseInPedHeightOffsetSpeed; // 132 + float m_fCloseInCarHeightOffset; + float m_fCloseInCarHeightOffsetSpeed; + float m_fDimensionOfHighestNearCar; + float m_fDistanceBeforeChanges; + float m_fFovSpeedOverOneFrame; + float m_fMinDistAwayFromCamWhenInterPolating; + float m_fPedBetweenCameraHeightOffset; + float m_fPlayerInFrontSyphonAngleOffSet; // 164 + float m_fRadiusForDead; + float m_fRealGroundDist; //used for follow ped mode + float m_fTargetBeta; + float m_fTimeElapsedFloat; + + float m_fTransitionBeta; + float m_fTrueBeta; + float m_fTrueAlpha; // 200 + float m_fInitialPlayerOrientation; //used for first person + + float Alpha; + float AlphaSpeed; + float FOV; + float FOVSpeed; + float Beta; + float BetaSpeed; + float Distance; // 232 + float DistanceSpeed; + float CA_MIN_DISTANCE; + float CA_MAX_DISTANCE; + float SpeedVar; + + // ped onfoot zoom distance + float m_fTargetZoomGroundOne; + float m_fTargetZoomGroundTwo; // 256 + float m_fTargetZoomGroundThree; + // ped onfoot alpha angle offset + float m_fTargetZoomOneZExtra; + float m_fTargetZoomTwoZExtra; + float m_fTargetZoomThreeZExtra; + + float m_fTargetZoomZCloseIn; + float m_fMinRealGroundDist; + float m_fTargetCloseInDist; + + CVector m_cvecTargetCoorsForFudgeInter; // 360 + CVector m_cvecCamFixedModeVector; // 372 + CVector m_cvecCamFixedModeSource; // 384 + CVector m_cvecCamFixedModeUpOffSet; // 396 + CVector m_vecLastAboveWaterCamPosition; //408 //helper for when the player has gone under the water + CVector m_vecBufferedPlayerBodyOffset; // 420 + + // The three vectors that determine this camera for this frame + CVector Front; // 432 // Direction of looking in + CVector Source; // Coors in world space + CVector SourceBeforeLookBehind; + CVector Up; // Just that + CVector m_arrPreviousVectors[NUMBER_OF_VECTORS_FOR_AVERAGE]; // used to average stuff + CEntity *CamTargetEntity; + + float m_fCameraDistance; + float m_fIdealAlpha; + float m_fPlayerVelocity; + CAutomobile *m_pLastCarEntered; // So interpolation works + CPed *m_pLastPedLookedAt;// So interpolation works + bool m_bFirstPersonRunAboutActive; + + + void GetVectorsReadyForRW(void); + CVector DoAverageOnVector(const CVector &vec); + float GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies); + void WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation); + void WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight); + bool RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation); + bool FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation); + void Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist); + void FixCamWhenObscuredByVehicle(const CVector &TargetCoors); + bool Using3rdPersonMouseCam(); + bool GetWeaponFirstPersonOn(); + + void Process_Debug(float *vec, float a, float b, float c); + void Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float); +}; +static_assert(sizeof(CCam) == 0x1A4, "CCam: wrong size"); +static_assert(offsetof(CCam, Alpha) == 0xA8, "CCam: error"); +static_assert(offsetof(CCam, Front) == 0x140, "CCam: error"); + +struct CCamPathSplines +{ + float m_arr_PathData[800]; +}; + +struct CTrainCamNode +{ + CVector m_cvecCamPosition; + CVector m_cvecPointToLookAt; + CVector m_cvecMinPointInRange; + CVector m_cvecMaxPointInRange; + float m_fDesiredFOV; + float m_fNearClip; +}; + +struct CQueuedMode +{ + int16 Mode; + float Duration; + int16 MinZoom; + int16 MaxZoom; +}; + +enum +{ + LOOKING_BEHIND, + LOOKING_LEFT, + LOOKING_RIGHT, + LOOKING_FORWARD, +}; + +enum +{ + // TODO: figure out + FADE_0, + FADE_1, // mid fade + FADE_2, + + FADE_OUT = 0, + FADE_IN, +}; + +enum +{ + MBLUR_NONE, + MBLUR_SNIPER, + MBLUR_NORMAL, + MBLUR_INTRO1, // green camera + MBLUR_INTRO2, // unused + MBLUR_INTRO3, // bank scene + MBLUR_INTRO4, // jail break scene + MBLUR_INTRO5, // explosion + MBLUR_INTRO6, // player shot + MBLUR_UNUSED, // pinkish +}; + +struct CCamera : public CPlaceable +{ + bool m_bAboveGroundTrainNodesLoaded; + bool m_bBelowGroundTrainNodesLoaded; + bool m_bCamDirectlyBehind; + bool m_bCamDirectlyInFront; + bool m_bCameraJustRestored; + bool m_bcutsceneFinished; + bool m_bCullZoneChecksOn; + bool m_bFirstPersonBeingUsed; + bool m_bJustJumpedOutOf1stPersonBecauseOfTarget; + bool m_bIdleOn; + bool m_bInATunnelAndABigVehicle; + bool m_bInitialNodeFound; + bool m_bInitialNoNodeStaticsSet; + bool m_bIgnoreFadingStuffForMusic; + bool m_bPlayerIsInGarage; + bool m_bJustCameOutOfGarage; + bool m_bJustInitalised; + bool m_bJust_Switched; + bool m_bLookingAtPlayer; + bool m_bLookingAtVector; + bool m_bMoveCamToAvoidGeom; + bool m_bObbeCinematicPedCamOn; + bool m_bObbeCinematicCarCamOn; + bool m_bRestoreByJumpCut; + bool m_bUseNearClipScript; + bool m_bStartInterScript; + bool m_bStartingSpline; + bool m_bTargetJustBeenOnTrain; + bool m_bTargetJustCameOffTrain; + bool m_bUseSpecialFovTrain; + bool m_bUseTransitionBeta; + bool m_bUseScriptZoomValuePed; + bool m_bUseScriptZoomValueCar; + bool m_bWaitForInterpolToFinish; + bool m_bItsOkToLookJustAtThePlayer; + bool m_bWantsToSwitchWidescreenOff; + bool m_WideScreenOn; + bool m_1rstPersonRunCloseToAWall; + bool m_bHeadBob; + bool m_bFailedCullZoneTestPreviously; + +bool m_FadeTargetIsSplashScreen; + + bool WorldViewerBeingUsed; + uint8 ActiveCam; + uint32 m_uiCamShakeStart; + uint32 m_uiFirstPersonCamLastInputTime; +// where are those? +//bool m_bVehicleSuspenHigh; +//bool m_bEnable1rstPersonCamCntrlsScript; +//bool m_bAllow1rstPersonWeaponsCamera; + + uint32 m_uiLongestTimeInMill; + uint32 m_uiNumberOfTrainCamNodes; + uint8 m_uiTransitionJUSTStarted; + uint8 m_uiTransitionState; // 0:one mode 1:transition + + uint32 m_uiTimeLastChange; + uint32 m_uiTimeWeEnteredIdle; + uint32 m_uiTimeTransitionStart; + uint32 m_uiTransitionDuration; + int m_BlurBlue; + int m_BlurGreen; + int m_BlurRed; + int m_BlurType; + +uint32 unknown; + int m_iWorkOutSpeedThisNumFrames; + int m_iNumFramesSoFar; + + + int m_iCurrentTrainCamNode; + int m_motionBlur; + int m_imotionBlurAddAlpha; + int m_iCheckCullZoneThisNumFrames; + int m_iZoneCullFrameNumWereAt; + int WhoIsInControlOfTheCamera; + + float CamFrontXNorm; + float CamFrontYNorm; + float CarZoomIndicator; + float CarZoomValue; + float CarZoomValueSmooth; + + float DistanceToWater; + float FOVDuringInter; + float LODDistMultiplier; + float GenerationDistMultiplier; + float m_fAlphaSpeedAtStartInter; + float m_fAlphaWhenInterPol; + float m_fAlphaDuringInterPol; + float m_fBetaDuringInterPol; + float m_fBetaSpeedAtStartInter; + float m_fBetaWhenInterPol; + float m_fFOVWhenInterPol; + float m_fFOVSpeedAtStartInter; + float m_fStartingBetaForInterPol; + float m_fStartingAlphaForInterPol; + float m_PedOrientForBehindOrInFront; + float m_CameraAverageSpeed; + float m_CameraSpeedSoFar; + float m_fCamShakeForce; + float m_fCarZoomValueScript; + float m_fFovForTrain; + float m_fFOV_Wide_Screen; + float m_fNearClipScript; + float m_fOldBetaDiff; + float m_fPedZoomValue; + + float m_fPedZoomValueScript; + float m_fPedZoomValueSmooth; + float m_fPositionAlongSpline; + float m_ScreenReductionPercentage; + float m_ScreenReductionSpeed; + float m_AlphaForPlayerAnim1rstPerson; + float Orientation; + float PedZoomIndicator; + float PlayerExhaustion; + float SoundDistUp, SoundDistLeft, SoundDistRight; + float SoundDistUpAsRead, SoundDistLeftAsRead, SoundDistRightAsRead; + float SoundDistUpAsReadOld, SoundDistLeftAsReadOld, SoundDistRightAsReadOld; + float m_fWideScreenReductionAmount; + float m_fStartingFOVForInterPol; + + // not static yet + float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls + float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls + float m_f3rdPersonCHairMultX; + float m_f3rdPersonCHairMultY; + + + CCam Cams[3]; + void *pToGarageWeAreIn; + void *pToGarageWeAreInForHackAvoidFirstPerson; + CQueuedMode m_PlayerMode; + CQueuedMode PlayerWeaponMode; + CVector m_PreviousCameraPosition; + CVector m_RealPreviousCameraPosition; + CVector m_cvecAimingTargetCoors; + CVector m_vecFixedModeVector; + + // one of those has to go + CVector m_vecFixedModeSource; + CVector m_vecFixedModeUpOffSet; +// CVector m_vecCutSceneOffset; + CVector m_cvecStartingSourceForInterPol; + CVector m_cvecStartingTargetForInterPol; + CVector m_cvecStartingUpForInterPol; + CVector m_cvecSourceSpeedAtStartInter; + CVector m_cvecTargetSpeedAtStartInter; + CVector m_cvecUpSpeedAtStartInter; + CVector m_vecSourceWhenInterPol; + CVector m_vecTargetWhenInterPol; + CVector m_vecUpWhenInterPol; + CVector m_vecClearGeometryVec; + + CVector m_vecGameCamPos; + CVector SourceDuringInter; + CVector TargetDuringInter; + CVector UpDuringInter; + RwCamera *m_pRwCamera; + CEntity *pTargetEntity; + CCamPathSplines m_arrPathArray[4]; + CTrainCamNode m_arrTrainCamNode[800]; + CMatrix m_cameraMatrix; + bool m_bGarageFixedCamPositionSet; + bool m_vecDoingSpecialInterPolation; + bool m_bScriptParametersSetForInterPol; + bool m_bFading; + bool m_bMusicFading; + CMatrix m_viewMatrix; + CVector m_vecFrustumNormals[4]; + CVector m_vecOldSourceForInter; + CVector m_vecOldFrontForInter; + CVector m_vecOldUpForInter; + + float m_vecOldFOVForInter; + float m_fFLOATingFade; + float m_fFLOATingFadeMusic; + float m_fTimeToFadeOut; + float m_fTimeToFadeMusic; + float m_fFractionInterToStopMovingTarget; + float m_fFractionInterToStopCatchUpTarget; + float m_fGaitSwayBuffer; + float m_fScriptPercentageInterToStopMoving; + float m_fScriptPercentageInterToCatchUp; + +uint32 m_fScriptTimeForInterPolation; + + +int16 m_iFadingDirection; +int m_iModeObbeCamIsInForCar; + int16 m_iModeToGoTo; + int16 m_iMusicFadingDirection; + int16 m_iTypeOfSwitch; + + uint32 m_uiFadeTimeStarted; + uint32 m_uiFadeTimeStartedMusic; + + static bool &m_bUseMouse3rdPerson; + + CMatrix &GetCameraMatrix(void) { return m_cameraMatrix; } + CVector &GetGameCamPosition(void) { return m_vecGameCamPos; } + bool IsPointVisible(const CVector ¢er, const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius, const CMatrix *mat); + bool IsBoxVisible(RwV3d *box, const CMatrix *mat); + int GetLookDirection(void); + + void Fade(float timeout, int16 direction); + int GetScreenFadeStatus(void); + void ProcessFade(void); + void ProcessMusicFade(void); + void SetFadeColour(uint8 r, uint8 g, uint8 b); + + void SetMotionBlur(int r, int g, int b, int a, int type); + void SetMotionBlurAlpha(int a); + void RenderMotionBlur(void); + void ClearPlayerWeaponMode(); + void CalculateDerivedValues(void); + + void DrawBordersForWideScreen(void); + void Restore(void); + void SetWidescreenOff(void); + + void dtor(void) { this->CCamera::~CCamera(); } +}; +static_assert(offsetof(CCamera, m_WideScreenOn) == 0x70, "CCamera: error"); +static_assert(offsetof(CCamera, WorldViewerBeingUsed) == 0x75, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiNumberOfTrainCamNodes) == 0x84, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiTransitionState) == 0x89, "CCamera: error"); +static_assert(offsetof(CCamera, m_uiTimeTransitionStart) == 0x94, "CCamera: error"); +static_assert(offsetof(CCamera, m_BlurBlue) == 0x9C, "CCamera: error"); +static_assert(offsetof(CCamera, Cams) == 0x1A4, "CCamera: error"); +static_assert(sizeof(CCamera) == 0xE9D8, "CCamera: wrong size"); +extern CCamera &TheCamera; diff --git a/src/core/CdStream.cpp b/src/core/CdStream.cpp new file mode 100644 index 00000000..255e46bb --- /dev/null +++ b/src/core/CdStream.cpp @@ -0,0 +1,529 @@ +#include <windows.h> +#include "common.h" +#include "patcher.h" +#include "CdStream.h" +#include "rwcore.h" +#include "RwHelper.h" + +#define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", __VA_ARGS__) +#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", __VA_ARGS__) + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + char field_C; + bool bLocked; + bool bInUse; + char _pad0; + int32 nStatus; + HANDLE hSemaphore; + HANDLE hFile; + OVERLAPPED Overlapped; +}; +VALIDATE_SIZE(CdReadInfo, 0x30); + +char gCdImageNames[MAX_CDIMAGES+1][64]; +int32 gNumImages; +int32 gNumChannels; + +HANDLE gImgFiles[MAX_CDIMAGES]; + +HANDLE _gCdStreamThread; +HANDLE gCdStreamSema; +DWORD _gCdStreamThreadId; + +CdReadInfo *gpReadInfo; +Queue gChannelRequestQ; + +int32 lastPosnRead; + +BOOL _gbCdStreamOverlapped; +BOOL _gbCdStreamAsync; +DWORD _gdwCdStreamFlags; + + +void +CdStreamInitThread(void) +{ + SetLastError(0); + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].hSemaphore = CreateSemaphore(nil, 0, 2, nil); + + if ( gpReadInfo[i].hSemaphore == nil ) + { + CDTRACE("failed to create sync semaphore"); + ASSERT(0); + return; + } + } + } + + gChannelRequestQ.items = (int32 *)LocalAlloc(LMEM_ZEROINIT, sizeof(int32) * (gNumChannels + 1)); + gChannelRequestQ.head = 0; + gChannelRequestQ.tail = 0; + gChannelRequestQ.size = gNumChannels + 1; + ASSERT(gChannelRequestQ.items != nil ); + + gCdStreamSema = CreateSemaphore(nil, 0, 5, "CdStream"); + + if ( gCdStreamSema == nil ) + { + CDTRACE("failed to create stream semaphore"); + ASSERT(0); + return; + } + + _gCdStreamThread = CreateThread(nil, 64*1024/*64KB*/, CdStreamThread, nil, CREATE_SUSPENDED, &_gCdStreamThreadId); + + if ( _gCdStreamThread == nil ) + { + CDTRACE("failed to create streaming thread"); + ASSERT(0); + return; + } + + SetThreadPriority(_gCdStreamThread, GetThreadPriority(GetCurrentThread()) - 1); + + ResumeThread(_gCdStreamThread); +} + +void +CdStreamInit(int32 numChannels) +{ + DWORD SectorsPerCluster; + DWORD BytesPerSector; + DWORD NumberOfFreeClusters; + DWORD TotalNumberOfClusters; + + GetDiskFreeSpace(nil, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters); + + _gdwCdStreamFlags = 0; + + if ( BytesPerSector <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= FILE_FLAG_NO_BUFFERING; + debug("Using no buffered loading for streaming\n"); + } + + _gbCdStreamOverlapped = TRUE; + + _gdwCdStreamFlags |= FILE_FLAG_OVERLAPPED; + + _gbCdStreamAsync = FALSE; + + void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, BytesPerSector); + ASSERT( pBuffer != nil ); + + SetLastError(0); + + gNumImages = 0; + + gNumChannels = numChannels; + + gpReadInfo = (CdReadInfo *)LocalAlloc(LMEM_ZEROINIT, sizeof(CdReadInfo) * numChannels); + ASSERT( gpReadInfo != nil ); + + CDDEBUG("read info %p", gpReadInfo); + + CdStreamAddImage("MODELS\\GTA3.IMG"); + + int32 nStatus = CdStreamRead(0, pBuffer, 0, 1); + + CdStreamRemoveImages(); + + if ( nStatus == STREAM_SUCCESS ) + { + _gbCdStreamAsync = TRUE; + + debug("Using async loading for streaming\n"); + } + else + { + _gdwCdStreamFlags &= ~FILE_FLAG_OVERLAPPED; + + _gbCdStreamOverlapped = FALSE; + + _gbCdStreamAsync = TRUE; + + debug("Using sync loading for streaming\n"); + } + + CdStreamInitThread(); + + ASSERT( pBuffer != nil ); + RwFreeAlign(pBuffer); +} + +uint32 +GetGTA3ImgSize(void) +{ + ASSERT( gImgFiles[0] != nil ); + return (uint32)GetFileSize(gImgFiles[0], nil); +} + +void +CdStreamShutdown(void) +{ + if ( _gbCdStreamAsync ) + { + LocalFree(gChannelRequestQ.items); + CloseHandle(gCdStreamSema); + CloseHandle(_gCdStreamThread); + + for ( int32 i = 0; i < gNumChannels; i++ ) + CloseHandle(gpReadInfo[i].hSemaphore); + } + + LocalFree(gpReadInfo); +} + + + +int32 +CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size) +{ + ASSERT( channel < gNumChannels ); + ASSERT( buffer != nil ); + + lastPosnRead = size + offset; + + ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES ); + HANDLE hImage = gImgFiles[_GET_INDEX(offset)]; + ASSERT( hImage != nil ); + + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->hFile = hImage; + + SetLastError(0); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->nSectorsToRead != 0 || pChannel->bInUse ) + return STREAM_NONE; + + pChannel->nStatus = STREAM_NONE; + pChannel->nSectorOffset = _GET_OFFSET(offset); + pChannel->nSectorsToRead = size; + pChannel->pBuffer = buffer; + pChannel->bLocked = 0; + + AddToQueue(&gChannelRequestQ, channel); + + if ( !ReleaseSemaphore(gCdStreamSema, 1, nil) ) + printf("Signal Sema Error\n"); + + return STREAM_SUCCESS; + } + + if ( _gbCdStreamOverlapped ) + { + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->Overlapped.Offset = _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE; + + if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, NULL, &pChannel->Overlapped) + && GetLastError() != ERROR_IO_PENDING ) + return STREAM_NONE; + else + return STREAM_SUCCESS; + } + + SetFilePointer(hImage, _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN); + + DWORD NumberOfBytesRead; + + if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, &NumberOfBytesRead, nil) ) + return STREAM_NONE; + else + return STREAM_SUCCESS; +} + +int32 +CdStreamGetStatus(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->bInUse ) + return STREAM_READING; + + if ( pChannel->nSectorsToRead != 0 ) + return STREAM_WAITING; + + if ( pChannel->nStatus != STREAM_NONE ) + { + int32 status = pChannel->nStatus; + + pChannel->nStatus = STREAM_NONE; + + return status; + } + + return STREAM_NONE; + } + + if ( _gbCdStreamOverlapped ) + { + ASSERT( pChannel->hFile != nil ); + if ( WaitForSingleObjectEx(pChannel->hFile, 0, TRUE) == WAIT_OBJECT_0 ) + return STREAM_NONE; + else + return STREAM_READING; + } + + return STREAM_NONE; +} + +int32 +CdStreamGetLastPosn(void) +{ + return lastPosnRead; +} + +int32 +CdStreamSync(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->nSectorsToRead != 0 ) + { + pChannel->bLocked = true; + + ASSERT( pChannel->hSemaphore != nil ); + + WaitForSingleObject(pChannel->hSemaphore, INFINITE); + } + + pChannel->bInUse = false; + + return pChannel->nStatus; + } + + DWORD NumberOfBytesTransferred; + + if ( _gbCdStreamOverlapped && pChannel->hFile ) + { + ASSERT(pChannel->hFile != nil ); + if ( GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) ) + return STREAM_NONE; + else + return STREAM_ERROR; + } + + return STREAM_NONE; +} + +void +AddToQueue(Queue *queue, int32 item) +{ + ASSERT( queue != nil ); + ASSERT( queue->items != nil ); + queue->items[queue->tail] = item; + + queue->tail = (queue->tail + 1) % queue->size; + + if ( queue->head == queue->tail ) + debug("Queue is full\n"); +} + +int32 +GetFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + return -1; + + ASSERT( queue->items != nil ); + return queue->items[queue->head]; +} + +void +RemoveFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + { + debug("Queue is empty\n"); + return; + } + + queue->head = (queue->head + 1) % queue->size; +} + +DWORD +WINAPI CdStreamThread(LPVOID lpThreadParameter) +{ + debug("Created cdstream thread\n"); + + while ( true ) + { + WaitForSingleObject(gCdStreamSema, INFINITE); + + int32 channel = GetFirstInQueue(&gChannelRequestQ); + ASSERT( channel < gNumChannels ); + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->bInUse = true; + + if ( pChannel->nStatus == STREAM_NONE ) + { + if ( _gbCdStreamOverlapped ) + { + pChannel->Overlapped.Offset = pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE; + + ASSERT(pChannel->hFile != nil ); + ASSERT(pChannel->pBuffer != nil ); + + DWORD NumberOfBytesTransferred; + + if ( ReadFile(pChannel->hFile, + pChannel->pBuffer, + pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE, + NULL, + &pChannel->Overlapped) ) + { + pChannel->nStatus = STREAM_NONE; + } + else if ( GetLastError() == ERROR_IO_PENDING + && GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) ) + { + pChannel->nStatus = STREAM_NONE; + } + else + { + pChannel->nStatus = STREAM_ERROR; + } + } + else + { + ASSERT(pChannel->hFile != nil ); + ASSERT(pChannel->pBuffer != nil ); + + SetFilePointer(pChannel->hFile, pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN); + + DWORD NumberOfBytesRead; + if ( ReadFile(pChannel->hFile, + pChannel->pBuffer, + pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE, + &NumberOfBytesRead, + NULL) ) + { + pChannel->nStatus = STREAM_NONE; + } + } + } + + RemoveFirstInQueue(&gChannelRequestQ); + + pChannel->nSectorsToRead = 0; + + if ( pChannel->bLocked ) + { + ASSERT( pChannel->hSemaphore != nil ); + ReleaseSemaphore(pChannel->hSemaphore, 1, NULL); + } + + pChannel->bInUse = false; + } +} + +bool +CdStreamAddImage(char const *path) +{ + ASSERT(path != nil); + ASSERT(gNumImages < MAX_CDIMAGES); + + SetLastError(0); + + gImgFiles[gNumImages] = CreateFile(path, + GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + _gdwCdStreamFlags | FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_READONLY, + nil); + + ASSERT( gImgFiles[gNumImages] != nil ); + if ( gImgFiles[gNumImages] == NULL ) + return false; + + strcpy(gCdImageNames[gNumImages], path); + + gNumImages++; + + return true; +} + +char * +CdStreamGetImageName(int32 cd) +{ + ASSERT(cd < MAX_CDIMAGES); + if ( gImgFiles[cd] != nil ) + return gCdImageNames[cd]; + + return nil; +} + +void +CdStreamRemoveImages(void) +{ + for ( int32 i = 0; i < gNumChannels; i++ ) + CdStreamSync(i); + + for ( int32 i = 0; i < gNumImages; i++ ) + { + SetLastError(0); + + CloseHandle(gImgFiles[i]); + gImgFiles[i] = nil; + } + + gNumImages = 0; +} + +int32 +CdStreamGetNumImages(void) +{ + return gNumImages; +} + + +STARTPATCHES + InjectHook(0x405B50, CdStreamInitThread, PATCH_JUMP); + InjectHook(0x405C80, CdStreamInit, PATCH_JUMP); + //InjectHook(0x405DB0, debug, PATCH_JUMP); + InjectHook(0x405DC0, GetGTA3ImgSize, PATCH_JUMP); + InjectHook(0x405DD0, CdStreamShutdown, PATCH_JUMP); + InjectHook(0x405E40, CdStreamRead, PATCH_JUMP); + InjectHook(0x405F90, CdStreamGetStatus, PATCH_JUMP); + InjectHook(0x406000, CdStreamGetLastPosn, PATCH_JUMP); + InjectHook(0x406010, CdStreamSync, PATCH_JUMP); + InjectHook(0x4060B0, AddToQueue, PATCH_JUMP); + InjectHook(0x4060F0, GetFirstInQueue, PATCH_JUMP); + InjectHook(0x406110, RemoveFirstInQueue, PATCH_JUMP); + InjectHook(0x406140, CdStreamThread, PATCH_JUMP); + InjectHook(0x406270, CdStreamAddImage, PATCH_JUMP); + InjectHook(0x4062E0, CdStreamGetImageName, PATCH_JUMP); + InjectHook(0x406300, CdStreamRemoveImages, PATCH_JUMP); + InjectHook(0x406370, CdStreamGetNumImages, PATCH_JUMP); +ENDPATCHES
\ No newline at end of file diff --git a/src/core/CdStream.h b/src/core/CdStream.h new file mode 100644 index 00000000..55507aa8 --- /dev/null +++ b/src/core/CdStream.h @@ -0,0 +1,46 @@ +#pragma once + +#define CDSTREAM_SECTOR_SIZE 2048 + +#define _GET_INDEX(a) (a >> 24) +#define _GET_OFFSET(a) (a & 0xFFFFFF) + +enum +{ + STREAM_NONE = uint8( 0), + STREAM_SUCCESS = uint8( 1), + STREAM_READING = uint8(-1), // 0xFF, + STREAM_ERROR = uint8(-2), // 0xFE, + STREAM_ERROR_NOCD = uint8(-3), // 0xFD, + STREAM_ERROR_WRONGCD = uint8(-4), // 0xFC, + STREAM_ERROR_OPENCD = uint8(-5), // 0xFB, + STREAM_WAITING = uint8(-6) // 0xFA, +}; + +struct Queue +{ + int32 *items; + int32 head; + int32 tail; + int32 size; +}; + +VALIDATE_SIZE(Queue, 0x10); + + +void CdStreamInitThread(void); +void CdStreamInit(int32 numChannels); +uint32 GetGTA3ImgSize(void); +void CdStreamShutdown(void); +int32 CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size); +int32 CdStreamGetStatus(int32 channel); +int32 CdStreamGetLastPosn(void); +int32 CdStreamSync(int32 channel); +void AddToQueue(Queue *queue, int32 item); +int32 GetFirstInQueue(Queue *queue); +void RemoveFirstInQueue(Queue *queue); +DWORD WINAPI CdStreamThread(LPVOID lpThreadParameter); +bool CdStreamAddImage(char const *path); +char *CdStreamGetImageName(int32 cd); +void CdStreamRemoveImages(void); +int32 CdStreamGetNumImages(void);
\ No newline at end of file diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp new file mode 100644 index 00000000..707b0e57 --- /dev/null +++ b/src/core/Clock.cpp @@ -0,0 +1,131 @@ +#include "common.h" +#include "patcher.h" +#include "Timer.h" +#include "Pad.h" +#include "Clock.h" +#include "Stats.h" + +_TODO("gbFastTime"); +bool &gbFastTime = *(bool*)0x95CDBB; + +uint8 &CClock::ms_nGameClockHours = *(uint8*)0x95CDA6; +uint8 &CClock::ms_nGameClockMinutes = *(uint8*)0x95CDC8; +uint16 &CClock::ms_nGameClockSeconds = *(uint16*)0x95CC7C; +uint8 &CClock::ms_Stored_nGameClockHours = *(uint8*)0x95CD7B; +uint8 &CClock::ms_Stored_nGameClockMinutes = *(uint8*)0x95CD9B; +uint16 &CClock::ms_Stored_nGameClockSeconds = *(uint16*)0x95CC9C; +uint32 &CClock::ms_nMillisecondsPerGameMinute = *(uint32*)0x8F2C64; +int32 &CClock::ms_nLastClockTick = *(int32*)0x9430E4; +bool &CClock::ms_bClockHasBeenStored = *(bool*)0x95CD82; + +void +CClock::Initialise(uint32 scale) +{ + debug("Initialising CClock...\n"); + ms_nGameClockHours = 12; + ms_nGameClockMinutes = 0; + ms_nGameClockSeconds = 0; + ms_nMillisecondsPerGameMinute = scale; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + ms_bClockHasBeenStored = false; + debug("CClock ready\n"); +} + +void +CClock::Update(void) +{ + if(CPad::GetPad(1)->GetRightShoulder1()) + { + ms_nGameClockMinutes += 8; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + + if(ms_nGameClockMinutes >= 60) + { + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + ms_nGameClockHours = 0; + } + + } + else if(CTimer::GetTimeInMilliseconds() - ms_nLastClockTick > ms_nMillisecondsPerGameMinute || gbFastTime) + { + ms_nGameClockMinutes++; + ms_nLastClockTick += ms_nMillisecondsPerGameMinute; + + if ( gbFastTime ) + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + + if(ms_nGameClockMinutes >= 60) + { + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + { + CStats::DaysPassed++; + ms_nGameClockHours = 0; + } + } + } + ms_nGameClockSeconds += + 60 + * (CTimer::GetTimeInMilliseconds() - ms_nLastClockTick) + / ms_nMillisecondsPerGameMinute; +} + +void +CClock::SetGameClock(uint8 h, uint8 m) +{ + ms_nGameClockHours = h; + ms_nGameClockMinutes = m; + ms_nGameClockSeconds = 0; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); +} + +int32 +CClock::GetGameClockMinutesUntil(uint8 h, uint8 m) +{ + int32 now, then; + now = ms_nGameClockHours*60 + ms_nGameClockMinutes; + then = h*60 + m; + if(then < now) + then += 24*60; + return then-now; +} + +bool +CClock::GetIsTimeInRange(uint8 h1, uint8 h2) +{ + if(h1 > h2) + return ms_nGameClockHours >= h1 || ms_nGameClockHours < h2; + else + return ms_nGameClockHours >= h1 && ms_nGameClockHours < h2; +} + +void +CClock::StoreClock(void) +{ + ms_Stored_nGameClockHours = ms_nGameClockHours; + ms_Stored_nGameClockMinutes = ms_nGameClockMinutes; + ms_Stored_nGameClockSeconds = ms_nGameClockSeconds; + ms_bClockHasBeenStored = true; +} + +void +CClock::RestoreClock(void) +{ + ms_nGameClockHours = ms_Stored_nGameClockHours; + ms_nGameClockMinutes = ms_Stored_nGameClockMinutes; + ms_nGameClockSeconds = ms_Stored_nGameClockSeconds; +} + + +STARTPATCHES + InjectHook(0x473370, CClock::Initialise, PATCH_JUMP); + InjectHook(0x473460, CClock::Update, PATCH_JUMP); + InjectHook(0x4733C0, CClock::SetGameClock, PATCH_JUMP); + InjectHook(0x4733F0, CClock::GetGameClockMinutesUntil, PATCH_JUMP); + InjectHook(0x473420, CClock::GetIsTimeInRange, PATCH_JUMP); + InjectHook(0x473540, CClock::StoreClock, PATCH_JUMP); + InjectHook(0x473570, CClock::RestoreClock, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Clock.h b/src/core/Clock.h new file mode 100644 index 00000000..e11b2293 --- /dev/null +++ b/src/core/Clock.h @@ -0,0 +1,31 @@ +#pragma once + +class CClock +{ + static uint8 &ms_nGameClockHours; + static uint8 &ms_nGameClockMinutes; + static uint16 &ms_nGameClockSeconds; + static uint8 &ms_Stored_nGameClockHours; + static uint8 &ms_Stored_nGameClockMinutes; + static uint16 &ms_Stored_nGameClockSeconds; + static uint32 &ms_nMillisecondsPerGameMinute; + static int32 &ms_nLastClockTick; + static bool &ms_bClockHasBeenStored; +public: + + static void Initialise(uint32 scale); + static void Update(void); + static void SetGameClock(uint8 h, uint8 m); + static int32 GetGameClockMinutesUntil(uint8 h, uint8 m); + static bool GetIsTimeInRange(uint8 h1, uint8 h2); + static void StoreClock(void); + static void RestoreClock(void); + + static uint8 GetHours(void) { return ms_nGameClockHours; } + static uint8 GetMinutes(void) { return ms_nGameClockMinutes; } + static int16 GetSeconds(void) { return ms_nGameClockSeconds; } + + + static uint8 &GetHoursRef(void) { return ms_nGameClockHours; } + static uint8 &GetMinutesRef(void) { return ms_nGameClockMinutes; } +}; diff --git a/src/core/Collision.cpp b/src/core/Collision.cpp new file mode 100644 index 00000000..d15ccca5 --- /dev/null +++ b/src/core/Collision.cpp @@ -0,0 +1,1887 @@ +#include "common.h" +#include "patcher.h" +#include "main.h" +#include "Lists.h" +#include "Game.h" +#include "Zones.h" +#include "General.h" +#include "ZoneCull.h" +#include "World.h" +#include "Entity.h" +#include "Train.h" +#include "Streaming.h" +#include "Pad.h" +#include "DMAudio.h" +#include "Population.h" +#include "FileLoader.h" +#include "Replay.h" +#include "CutsceneMgr.h" +#include "RenderBuffer.h" +#include "SurfaceTable.h" +#include "Collision.h" + +enum Direction +{ + DIR_X_POS, + DIR_X_NEG, + DIR_Y_POS, + DIR_Y_NEG, + DIR_Z_POS, + DIR_Z_NEG, +}; + +eLevelName &CCollision::ms_collisionInMemory = *(eLevelName*)0x8F6250; +CLinkList<CColModel*> &CCollision::ms_colModelCache = *(CLinkList<CColModel*>*)0x95CB58; + +void +CCollision::Init(void) +{ + ms_colModelCache.Init(NUMCOLCACHELINKS); + ms_collisionInMemory = LEVEL_NONE; +} + +void +CCollision::Shutdown(void) +{ + ms_colModelCache.Shutdown(); +} + +void +CCollision::Update(void) +{ + CVector playerCoors; + playerCoors = FindPlayerCoors(); + eLevelName level = CTheZones::m_CurrLevel; + bool forceLevelChange = false; + + if(CTimer::GetTimeInMilliseconds() < 2000 || CCutsceneMgr::IsCutsceneProcessing()) + return; + + // hardcode a level if there are no zones + if(level == LEVEL_NONE){ + if(CGame::currLevel == LEVEL_INDUSTRIAL && + playerCoors.x < 400.0f){ + level = LEVEL_COMMERCIAL; + forceLevelChange = true; + }else if(CGame::currLevel == LEVEL_SUBURBAN && + playerCoors.x > -450.0f && playerCoors.y < -1400.0f){ + level = LEVEL_COMMERCIAL; + forceLevelChange = true; + }else{ + if(playerCoors.x > 800.0f){ + level = LEVEL_INDUSTRIAL; + forceLevelChange = true; + }else if(playerCoors.x < -800.0f){ + level = LEVEL_SUBURBAN; + forceLevelChange = true; + } + } + } + if(level != LEVEL_NONE && level != CGame::currLevel) + CGame::currLevel = level; + if(ms_collisionInMemory != CGame::currLevel) + LoadCollisionWhenINeedIt(forceLevelChange); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); +} + +eLevelName +GetCollisionInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + int level; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + level = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel()->level; + if(level != LEVEL_NONE) + return (eLevelName)level; + } + return LEVEL_NONE; +} + +// Get a level this sector is in based on collision models +eLevelName +GetCollisionInSector(CSector §) +{ + int level; + + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS]); + if(level == LEVEL_NONE) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + if(level == LEVEL_NONE) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS]); + if(level == LEVEL_NONE) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + if(level == LEVEL_NONE) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES]); + if(level == LEVEL_NONE) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + return (eLevelName)level; +} + +void +CCollision::LoadCollisionWhenINeedIt(bool forceChange) +{ + eLevelName level, l; + bool multipleLevels; + CVector playerCoors; + CVehicle *veh; + CEntryInfoNode *ei; + int sx, sy; + int xmin, xmax, ymin, ymax; + int x, y; + + level = LEVEL_NONE; + + playerCoors = FindPlayerCoors(); + sx = CWorld::GetSectorIndexX(playerCoors.x); + sy = CWorld::GetSectorIndexY(playerCoors.y); + multipleLevels = false; + + veh = FindPlayerVehicle(); + if(veh && veh->IsTrain()){ + if(((CTrain*)veh)->m_doorState != TRAIN_DOOR_STATE2) + return ; + }else if(playerCoors.z < 4.0f && !CCullZones::DoINeedToLoadCollision()) + return; + + // Figure out whose level's collisions we're most likely to be interested in + if(!forceChange){ + if(veh && veh->IsBoat()){ + // on water we expect to be between levels + multipleLevels = true; + }else{ + xmin = max(sx - 1, 0); + xmax = min(sx + 1, NUMSECTORS_X-1); + ymin = max(sy - 1, 0); + ymax = min(sy + 1, NUMSECTORS_Y-1); + + for(x = xmin; x <= xmax; x++) + for(y = ymin; y <= ymax; y++){ + l = GetCollisionInSector(*CWorld::GetSector(x, y)); + if(l != LEVEL_NONE){ + if(level == LEVEL_NONE) + level = l; + if(level != l) + multipleLevels = true; + } + } + } + + if(multipleLevels && veh && veh->IsBoat()) + for(ei = veh->m_entryInfoList.first; ei; ei = ei->next){ + level = GetCollisionInSector(*ei->sector); + if(level != LEVEL_NONE) + break; + } + } + + if(level == CGame::currLevel || forceChange){ + CTimer::Stop(); + DMAudio.SetEffectsFadeVol(0); + CPad::StopPadsShaking(); + LoadCollisionScreen(CGame::currLevel); + DMAudio.Service(); + CPopulation::DealWithZoneChange(ms_collisionInMemory, CGame::currLevel, false); + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::TidyUpMemory(true, true); + CFileLoader::LoadCollisionFromDatFile(CGame::currLevel); + ms_collisionInMemory = CGame::currLevel; + CReplay::EmptyReplayBuffer(); + if(CGame::currLevel != LEVEL_NONE) + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CGame::TidyUpMemory(true, true); + CTimer::Update(); + DMAudio.SetEffectsFadeVol(127); + } +} + +void +CCollision::SortOutCollisionAfterLoad(void) +{ + if(ms_collisionInMemory == CGame::currLevel) + return; + + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + if(CGame::currLevel != LEVEL_NONE){ + CFileLoader::LoadCollisionFromDatFile(CGame::currLevel); + if(!CGame::playingIntro) + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + } + ms_collisionInMemory = CGame::currLevel; + CGame::TidyUpMemory(true, false); +} + +void +CCollision::LoadCollisionScreen(eLevelName level) +{ + static char *levelNames[4] = { + "", + "IND_ZON", + "COM_ZON", + "SUB_ZON" + }; + + // Why twice? + LoadingIslandScreen(levelNames[level]); + LoadingIslandScreen(levelNames[level]); +} + +// +// Test +// + + +bool +CCollision::TestSphereSphere(const CColSphere &s1, const CColSphere &s2) +{ + float d = s1.radius + s2.radius; + return (s1.center - s2.center).MagnitudeSqr() < d*d; +} + +bool +CCollision::TestSphereBox(const CColSphere &sph, const CColBox &box) +{ + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + return true; +} + +bool +CCollision::TestLineBox(const CColLine &line, const CColBox &box) +{ + float t, x, y, z; + // If either line point is in the box, we have a collision + if(line.p0.x > box.min.x && line.p0.x < box.max.x && + line.p0.y > box.min.y && line.p0.y < box.max.y && + line.p0.z > box.min.z && line.p0.z < box.max.z) + return true; + if(line.p1.x > box.min.x && line.p1.x < box.max.x && + line.p1.y > box.min.y && line.p1.y < box.max.y && + line.p1.z > box.min.z && line.p1.z < box.max.z) + return true; + + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // same test with max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + return false; +} + +bool +CCollision::TestVerticalLineBox(const CColLine &line, const CColBox &box) +{ + if(line.p0.x <= box.min.x) return false; + if(line.p0.y <= box.min.y) return false; + if(line.p0.x >= box.max.x) return false; + if(line.p0.y >= box.max.y) return false; + if(line.p0.z < line.p1.z){ + if(line.p0.z > box.max.z) return false; + if(line.p1.z < box.min.z) return false; + }else{ + if(line.p1.z > box.max.z) return false; + if(line.p0.z < box.min.z) return false; + } + return true; +} + +bool +CCollision::TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal); + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + CVector2D vec1, vec2, vec3, vect; + + // We do the test in 2D. With the plane direction we + // can figure out how to project the vectors. + // normal = (c-a) x (b-a) + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + // This is our triangle: + // 3-------2 + // \ P / + // \ / + // \ / + // 1 + // We can use the "2d cross product" to check on which side + // a vector is of another. Test is true if point is inside of all edges. + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + return true; +} + +// Test if line segment intersects with sphere. +// If the first point is inside the sphere this test does not register a collision! +// The code is reversed from the original code and rather ugly, see Process for a clear version. +// TODO: actually rewrite this mess +bool +CCollision::TestLineSphere(const CColLine &line, const CColSphere &sph) +{ + CVector v01 = line.p1 - line.p0; // vector from p0 to p1 + CVector v0c = sph.center - line.p0; // vector from p0 to center + float linesq = v01.MagnitudeSqr(); + // I leave in the strange -2 factors even though they serve no real purpose + float projline = -2.0f * DotProduct(v01, v0c); // project v0c onto line + // Square of tangent from p0 multiplied by line length so we can compare with projline. + // The length of the tangent would be this: sqrt((c-p0)^2 - r^2). + // Negative if p0 is inside the sphere! This breaks the test! + float tansq = 4.0f * linesq * + (sph.center.MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius); + float diffsq = projline*projline - tansq; + // if diffsq < 0 that means the line is a passant, so no intersection + if(diffsq < 0.0f) + return false; + // projline (negative in GTA for some reason) is the point on the line + // in the middle of the two intersection points (startin from p0). + // sqrt(diffsq) somehow works out to be the distance from that + // midpoint to the intersection points. + // So subtract that and get rid of the awkward scaling: + float f = (-projline - sqrt(diffsq)) / (2.0f*linesq); + // f should now be in range [0, 1] for [p0, p1] + return f >= 0.0f && f <= 1.0f; +} + +bool +CCollision::TestSphereTriangle(const CColSphere &sphere, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ + // If sphere and plane don't intersect, no collision + if(fabs(plane.CalcPoint(sphere.center)) > sphere.radius) + return false; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // calculate two orthogonal basis vectors for the triangle + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector vec1 = CrossProduct(vec2, plane.normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + if(testcase == 1){ + // closest to a vertex + if(insideAB) dist = (sphere.center - vc).Magnitude(); + else if(insideAC) dist = (sphere.center - vb).Magnitude(); + else if(insideBC) dist = (sphere.center - va).Magnitude(); + else assert(0); + }else if(testcase == 2){ + // closest to an edge + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center); + else assert(0); + }else if(testcase == 3){ + // center is in triangle + return true; + }else + assert(0); // front fell off + + return dist < sphere.radius; +} + +bool +CCollision::TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough) +{ + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD) + if(TestLineSphere(newline, model.spheres[i])) + return true; + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD) + if(TestLineBox(newline, model.boxes[i])) + return true; + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD) + if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i])) + return true; + + return false; +} + + +// +// Process +// + +// For Spheres mindist is the squared distance to its center +// For Lines mindist is between [0,1] + +bool +CCollision::ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq) +{ + CVector dist = s1.center - s2.center; + float d = dist.Magnitude() - s2.radius; // distance from s1's center to s2 + float dc = d < 0.0f ? 0.0f : d; // clamp to zero, i.e. if s1's center is inside s2 + // no collision if sphere is not close enough + if(mindistsq <= dc*dc || s1.radius <= dc) + return false; + dist.Normalise(); + point.point = s1.center - dist*dc; + point.normal = dist; + point.surfaceA = s1.surface; + point.pieceA = s1.piece; + point.surfaceB = s2.surface; + point.pieceB = s2.piece; + point.depth = s1.radius - d; // sphere overlap + mindistsq = dc*dc; // collision radius + return true; +} + +bool +CCollision::ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq) +{ + CVector p; + CVector dist; + + // GTA's code is too complicated, uses a huge 3x3x3 if statement + // we can simplify the structure a lot + + // first make sure we have a collision at all + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + + // Now find out where the sphere center lies in relation to all the sides + int xpos = sph.center.x < box.min.x ? 1 : + sph.center.x > box.max.x ? 2 : + 0; + int ypos = sph.center.y < box.min.y ? 1 : + sph.center.y > box.max.y ? 2 : + 0; + int zpos = sph.center.z < box.min.z ? 1 : + sph.center.z > box.max.z ? 2 : + 0; + + if(xpos == 0 && ypos == 0 && zpos == 0){ + // sphere is inside the box + p = (box.min + box.max)*0.5f; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + point.normal = dist * (1.0f/sqrt(lensq)); + point.point = sph.center - point.normal; + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; + + // find absolute distance to the closer side in each dimension + float dx = dist.x > 0.0f ? + box.max.x - sph.center.x : + sph.center.x - box.min.x; + float dy = dist.y > 0.0f ? + box.max.y - sph.center.y : + sph.center.y - box.min.y; + float dz = dist.z > 0.0f ? + box.max.z - sph.center.z : + sph.center.z - box.min.z; + // collision depth is maximum of that: + if(dx > dy && dx > dz) + point.depth = dx; + else if(dy > dz) + point.depth = dy; + else + point.depth = dz; + return true; + } + }else{ + // sphere is outside. + // closest point on box: + p.x = xpos == 1 ? box.min.x : + xpos == 2 ? box.max.x : + sph.center.x; + p.y = ypos == 1 ? box.min.y : + ypos == 2 ? box.max.y : + sph.center.y; + p.z = zpos == 1 ? box.min.z : + zpos == 2 ? box.max.z : + sph.center.z; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + float len = sqrt(lensq); + point.point = p; + point.normal = dist * (1.0f/len); + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; + point.depth = sph.radius - len; + mindistsq = lensq; + return true; + } + } + return false; +} + +bool +CCollision::ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist) +{ + float mint, t, x, y, z; + CVector normal; + CVector p; + + mint = 1.0f; + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.min.x, y, z); + normal = CVector(-1.0f, 0.0f, 0.0f); + } + } + } + + // max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.max.x, y, z); + normal = CVector(1.0f, 0.0f, 0.0f); + } + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.min.y, z); + normal = CVector(0.0f, -1.0f, 0.0f); + } + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.max.y, z); + normal = CVector(0.0f, 1.0f, 0.0f); + } + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.min.z); + normal = CVector(0.0f, 0.0f, -1.0f); + } + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.max.z); + normal = CVector(0.0f, 0.0f, 1.0f); + } + } + } + + if(mint >= mindist) + return false; + + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = box.surface; + point.pieceB = box.piece; + mindist = mint; + + return true; +} + +// If line.p0 lies inside sphere, no collision is registered. +bool +CCollision::ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist) +{ + CVector v01 = line.p1 - line.p0; + CVector v0c = sphere.center - line.p0; + float linesq = v01.MagnitudeSqr(); + // project v0c onto v01, scaled by |v01| this is the midpoint of the two intersections + float projline = DotProduct(v01, v0c); + // tangent of p0 to sphere, scaled by linesq just like projline^2 + float tansq = (v0c.MagnitudeSqr() - sphere.radius*sphere.radius) * linesq; + // this works out to be the square of the distance between the midpoint and the intersections + float diffsq = projline*projline - tansq; + // no intersection + if(diffsq < 0.0f) + return false; + // point of first intersection, in range [0,1] between p0 and p1 + float t = (projline - sqrt(diffsq)) / linesq; + // if not on line or beyond mindist, no intersection + if(t < 0.0f || t > 1.0f || t >= mindist) + return false; + point.point = line.p0 + v01*t; + point.normal = point.point - sphere.center; + point.normal.Normalise(); + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = sphere.surface; + point.pieceB = sphere.piece; + mindist = t; + return true; +} + +bool +CCollision::ProcessVerticalLineTriangle(const CColLine &line, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist, CStoredCollPoly *poly) +{ + float t; + CVector normal; + + const CVector &p0 = line.p0; + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // early out bound rect test + if(p0.x < va.x && p0.x < vb.x && p0.x < vc.x) return false; + if(p0.x > va.x && p0.x > vb.x && p0.x > vc.x) return false; + if(p0.y < va.y && p0.y < vb.y && p0.y < vc.y) return false; + if(p0.y > va.y && p0.y > vb.y && p0.y > vc.y) return false; + + plane.GetNormal(normal); + // if points are on the same side, no collision + if(plane.CalcPoint(p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + float h = (line.p1 - p0).z; + t = -plane.CalcPoint(p0) / (h * normal.z); + // early out if we're beyond the mindist + if(t >= mindist) + return false; + CVector p(p0.x, p0.y, p0.z + h*t); + + CVector2D vec1, vec2, vec3, vect; + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + if(poly){ + poly->verts[0] = va; + poly->verts[1] = vb; + poly->verts[2] = vc; + poly->valid = true; + } + mindist = t; + return true; +} + +bool +CCollision::ProcessLineTriangle(const CColLine &line , + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist) +{ + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / DotProduct(line.p1 - line.p0, normal); + // early out if we're beyond the mindist + if(t >= mindist) + return false; + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + CVector2D vec1, vec2, vec3, vect; + + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + mindist = t; + return true; +} + +bool +CCollision::ProcessSphereTriangle(const CColSphere &sphere, + const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindistsq) +{ + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + float distsq = planedist*planedist; + if(fabs(planedist) > sphere.radius || distsq > mindistsq) + return false; + + const CVector &va = verts[tri.a]; + const CVector &vb = verts[tri.b]; + const CVector &vc = verts[tri.c]; + + // calculate two orthogonal basis vectors for the triangle + CVector normal; + plane.GetNormal(normal); + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector vec1 = CrossProduct(vec2, normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + CVector p; + if(testcase == 1){ + // closest to a vertex + if(insideAB) p = vc; + else if(insideAC) p = vb; + else if(insideBC) p = va; + else assert(0); + dist = (sphere.center - p).Magnitude(); + }else if(testcase == 2){ + // closest to an edge + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center, p); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center, p); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center, p); + else assert(0); + }else if(testcase == 3){ + // center is in triangle + dist = fabs(planedist); + p = sphere.center - normal*planedist; + }else + assert(0); // front fell off + + if(dist >= sphere.radius || dist*dist >= mindistsq) + return false; + + point.point = p; + point.normal = sphere.center - p; + point.normal.Normalise(); + point.surfaceA = sphere.surface; + point.pieceA = sphere.piece; + point.surfaceB = tri.surface; + point.pieceB = 0; + point.depth = sphere.radius - dist; + mindistsq = dist*dist; + return true; +} + +bool +CCollision::ProcessLineOfSight(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough) +{ + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD) + ProcessLineSphere(newline, model.spheres[i], point, coldist); + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD) + ProcessLineBox(newline, model.boxes[i], point, coldist); + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD) + ProcessLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist); + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + mindist = coldist; + return true; + } + return false; +} + +bool +CCollision::ProcessVerticalLine(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + static CStoredCollPoly TempStoredPoly; + int i; + + // transform line to model space + // Why does the game seem to do this differently than above? + CColLine newline(MultiplyInverse(matrix, line.p0), MultiplyInverse(matrix, line.p1)); + newline.p1.x = newline.p0.x; + newline.p1.y = newline.p0.y; + + if(!TestVerticalLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++) + if(!ignoreSeeThrough || model.spheres[i].surface != SURFACE_GLASS && model.spheres[i].surface != SURFACE_SCAFFOLD) + ProcessLineSphere(newline, model.spheres[i], point, coldist); + + for(i = 0; i < model.numBoxes; i++) + if(!ignoreSeeThrough || model.boxes[i].surface != SURFACE_GLASS && model.boxes[i].surface != SURFACE_SCAFFOLD) + ProcessLineBox(newline, model.boxes[i], point, coldist); + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + for(i = 0; i < model.numTriangles; i++) + if(!ignoreSeeThrough || model.triangles[i].surface != SURFACE_GLASS && model.triangles[i].surface != SURFACE_SCAFFOLD) + ProcessVerticalLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist, &TempStoredPoly); + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + if(poly && TempStoredPoly.valid){ + *poly = TempStoredPoly; + poly->verts[0] = matrix * poly->verts[0]; + poly->verts[1] = matrix * poly->verts[1]; + poly->verts[2] = matrix * poly->verts[2]; + } + mindist = coldist; + return true; + } + return false; +} + +enum { + MAXNUMSPHERES = 128, + MAXNUMBOXES = 32, + MAXNUMLINES = 16, + MAXNUMTRIS = 600 +}; + +// This checks model A's spheres and lines against model B's spheres, boxes and triangles. +// Returns the number of A's spheres that collide. +// Returned ColPoints are in world space. +// NB: lines do not seem to be supported very well, use with caution +int32 +CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA, + const CMatrix &matrixB, CColModel &modelB, + CColPoint *spherepoints, CColPoint *linepoints, float *linedists) +{ + static int aSphereIndicesA[MAXNUMSPHERES]; + static int aLineIndicesA[MAXNUMLINES]; + static int aSphereIndicesB[MAXNUMSPHERES]; + static int aBoxIndicesB[MAXNUMBOXES]; + static int aTriangleIndicesB[MAXNUMTRIS]; + static bool aCollided[MAXNUMLINES]; + static CColSphere aSpheresA[MAXNUMSPHERES]; + static CColLine aLinesA[MAXNUMLINES]; + static CMatrix matAB, matBA; + CColSphere s; + int i, j; + + assert(modelA.numSpheres <= MAXNUMSPHERES); + assert(modelA.numLines <= MAXNUMLINES); + + // From model A space to model B space + matAB = Invert(matrixB, matAB) * matrixA; + + CColSphere bsphereAB; // bounding sphere of A in B space + bsphereAB.Set(modelA.boundingSphere.radius, matAB * modelA.boundingSphere.center); + if(!TestSphereBox(bsphereAB, modelB.boundingBox)) + return 0; + // B to A space + matBA = Invert(matrixA, matBA) * matrixB; + + // transform modelA's spheres and lines to B space + for(i = 0; i < modelA.numSpheres; i++){ + CColSphere &s = modelA.spheres[i]; + aSpheresA[i].Set(s.radius, matAB * s.center, s.surface, s.piece); + } + for(i = 0; i < modelA.numLines; i++) + aLinesA[i].Set(matAB * modelA.lines[i].p0, matAB * modelA.lines[i].p1); + + // Test them against model B's bounding volumes + int numSpheresA = 0; + int numLinesA = 0; + for(i = 0; i < modelA.numSpheres; i++) + if(TestSphereBox(aSpheresA[i], modelB.boundingBox)) + aSphereIndicesA[numSpheresA++] = i; + // no actual check??? + for(i = 0; i < modelA.numLines; i++) + aLineIndicesA[numLinesA++] = i; + // No collision + if(numSpheresA == 0 && numLinesA == 0) + return 0; + + // Check model B against A's bounding volumes + int numSpheresB = 0; + int numBoxesB = 0; + int numTrianglesB = 0; + for(i = 0; i < modelB.numSpheres; i++){ + s.Set(modelB.spheres[i].radius, matBA * modelB.spheres[i].center); + if(TestSphereBox(s, modelA.boundingBox)) + aSphereIndicesB[numSpheresB++] = i; + } + for(i = 0; i < modelB.numBoxes; i++) + if(TestSphereBox(bsphereAB, modelB.boxes[i])) + aBoxIndicesB[numBoxesB++] = i; + CalculateTrianglePlanes(&modelB); + for(i = 0; i < modelB.numTriangles; i++) + if(TestSphereTriangle(bsphereAB, modelB.vertices, modelB.triangles[i], modelB.trianglePlanes[i])) + aTriangleIndicesB[numTrianglesB++] = i; + assert(numSpheresB <= MAXNUMSPHERES); + assert(numBoxesB <= MAXNUMBOXES); + assert(numTrianglesB <= MAXNUMTRIS); + // No collision + if(numSpheresB == 0 && numBoxesB == 0 && numTrianglesB == 0) + return 0; + + // We now have the collision volumes in A and B that are worth processing. + + // Process A's spheres against B's collision volumes + int numCollisions = 0; + for(i = 0; i < numSpheresA; i++){ + float coldist = 1.0e24f; + bool hasCollided = false; + + for(j = 0; j < numSpheresB; j++) + hasCollided |= ProcessSphereSphere( + aSpheresA[aSphereIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numBoxesB; j++) + hasCollided |= ProcessSphereBox( + aSpheresA[aSphereIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numTrianglesB; j++) + hasCollided |= ProcessSphereTriangle( + aSpheresA[aSphereIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + spherepoints[numCollisions], coldist); + if(hasCollided) + numCollisions++; + } + for(i = 0; i < numCollisions; i++){ + spherepoints[i].point = matrixB * spherepoints[i].point; + spherepoints[i].normal = Multiply3x3(matrixB, spherepoints[i].normal); + } + + // And the same thing for the lines in A + for(i = 0; i < numLinesA; i++){ + aCollided[i] = false; + + for(j = 0; j < numSpheresB; j++) + aCollided[i] |= ProcessLineSphere( + aLinesA[aLineIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numBoxesB; j++) + aCollided[i] |= ProcessLineBox( + aLinesA[aLineIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numTrianglesB; j++) + aCollided[i] |= ProcessLineTriangle( + aLinesA[aLineIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + } + for(i = 0; i < numLinesA; i++) + if(aCollided[i]){ + j = aLineIndicesA[i]; + linepoints[j].point = matrixB * linepoints[j].point; + linepoints[j].normal = Multiply3x3(matrixB, linepoints[j].normal); + } + + return numCollisions; // sphere collisions +} + + +// +// Misc +// + +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // Between 0 and len we're above the line. + // if not, calculate distance to endpoint + if(dot <= 0.0f) + return (*point - *l0).Magnitude(); + if(dot >= lensq) + return (*point - *l1).Magnitude(); + // distance to line + return sqrt((*point - *l0).MagnitudeSqr() - dot*dot/lensq); +} + +// same as above but also return the point on the line +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // find out which point we're closest to + if(dot <= 0.0f) + closest = *l0; + else if(dot >= lensq) + closest = *l1; + else + closest = *l0 + (*l1 - *l0)*(dot/lensq); + // this is the distance + return (*point - closest).Magnitude(); +} + +void +CCollision::CalculateTrianglePlanes(CColModel *model) +{ + if(model->numTriangles == 0) + return; + + CLink<CColModel*> *lptr; + if(model->trianglePlanes){ + // re-insert at front so it's not removed again soon + lptr = model->GetLinkPtr(); + lptr->Remove(); + ms_colModelCache.head.Insert(lptr); + }else{ + assert(model); + lptr = ms_colModelCache.Insert(model); + if(lptr == nil){ + // make room if we have to, remove last in list + lptr = ms_colModelCache.tail.prev; + assert(lptr); + assert(lptr->item); + lptr->item->RemoveTrianglePlanes(); + ms_colModelCache.Remove(lptr); + // now this cannot fail + lptr = ms_colModelCache.Insert(model); + assert(lptr); + } + model->CalculateTrianglePlanes(); + model->SetLinkPtr(lptr); + } +} + +void +CCollision::DrawColModel(const CMatrix &mat, const CColModel &colModel) +{ +} + +void +CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id) +{ + int i; + int s; + float f; + CVector verts[8]; + CVector min, max; + int r, g, b; + RwImVertexIndex *iptr; + RwIm3DVertex *vptr; + + RenderBuffer::ClearRenderBuffer(); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); +extern int gDbgSurf; + + for(i = 0; i < colModel.numTriangles; i++){ + colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a); + colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b); + colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c); + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + verts[2] = mat * verts[2]; + + // TODO: surface + r = 255; + g = 128; + b = 0; + + s = colModel.triangles[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + // TODO: make some surface types flicker? +//if(s != gDbgSurf) continue; + + if(s > SURFACE_32){ + r = CGeneral::GetRandomNumber(); + g = CGeneral::GetRandomNumber(); + b = CGeneral::GetRandomNumber(); + printf("Illegal surfacetype:%d on MI:%d\n", s, id); + } + + RenderBuffer::StartStoring(6, 3, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 0; iptr[4] = 2; iptr[5] = 1; + RenderBuffer::StopStoring(); + } + + for(i = 0; i < colModel.numBoxes; i++){ + min = colModel.boxes[i].min; + max = colModel.boxes[i].max; + + verts[0] = mat * CVector(min.x, min.y, min.z); + verts[1] = mat * CVector(min.x, min.y, max.z); + verts[2] = mat * CVector(min.x, max.y, min.z); + verts[3] = mat * CVector(min.x, max.y, max.z); + verts[4] = mat * CVector(max.x, min.y, min.z); + verts[5] = mat * CVector(max.x, min.y, max.z); + verts[6] = mat * CVector(max.x, max.y, min.z); + verts[7] = mat * CVector(max.x, max.y, max.z); + + s = colModel.boxes[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + // TODO: make some surface types flicker? +//if(s != gDbgSurf) continue; + + RenderBuffer::StartStoring(36, 8, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[3], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[4], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[5], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[6], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[7], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetU(&vptr[3], 0.0f); + RwIm3DVertexSetV(&vptr[3], 0.0f); + RwIm3DVertexSetU(&vptr[4], 0.0f); + RwIm3DVertexSetV(&vptr[4], 1.0f); + RwIm3DVertexSetU(&vptr[5], 1.0f); + RwIm3DVertexSetV(&vptr[5], 1.0f); + RwIm3DVertexSetU(&vptr[6], 0.0f); + RwIm3DVertexSetV(&vptr[6], 1.0f); + RwIm3DVertexSetU(&vptr[7], 1.0f); + RwIm3DVertexSetV(&vptr[7], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + RwIm3DVertexSetPos(&vptr[3], verts[3].x, verts[3].y, verts[3].z); + RwIm3DVertexSetPos(&vptr[4], verts[4].x, verts[4].y, verts[4].z); + RwIm3DVertexSetPos(&vptr[5], verts[5].x, verts[5].y, verts[5].z); + RwIm3DVertexSetPos(&vptr[6], verts[6].x, verts[6].y, verts[6].z); + RwIm3DVertexSetPos(&vptr[7], verts[7].x, verts[7].y, verts[7].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 1; iptr[4] = 3; iptr[5] = 2; + iptr[6] = 1; iptr[7] = 5; iptr[8] = 7; + iptr[9] = 1; iptr[10] = 7; iptr[11] = 3; + iptr[12] = 2; iptr[13] = 3; iptr[14] = 7; + iptr[15] = 2; iptr[16] = 7; iptr[17] = 6; + iptr[18] = 0; iptr[19] = 5; iptr[20] = 1; + iptr[21] = 0; iptr[22] = 4; iptr[23] = 5; + iptr[24] = 0; iptr[25] = 2; iptr[26] = 4; + iptr[27] = 2; iptr[28] = 6; iptr[29] = 4; + iptr[30] = 4; iptr[31] = 6; iptr[32] = 7; + iptr[33] = 4; iptr[34] = 7; iptr[35] = 5; + RenderBuffer::StopStoring(); + } + + RenderBuffer::RenderStuffInBuffer(); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +} + + +/* + * ColModel code + */ + +void +CColSphere::Set(float radius, const CVector ¢er, uint8 surf, uint8 piece) +{ + this->radius = radius; + this->center = center; + this->surface = surf; + this->piece = piece; +} + +void +CColBox::Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece) +{ + this->min = min; + this->max = max; + this->surface = surf; + this->piece = piece; +} + +void +CColLine::Set(const CVector &p0, const CVector &p1) +{ + this->p0 = p0; + this->p1 = p1; +} + +void +CColTriangle::Set(const CVector *, int a, int b, int c, uint8 surf, uint8 piece) +{ + this->a = a; + this->b = b; + this->c = c; + this->surface = surf; +} + +void +CColTrianglePlane::Set(const CVector *v, CColTriangle &tri) +{ + const CVector &va = v[tri.a]; + const CVector &vb = v[tri.b]; + const CVector &vc = v[tri.c]; + + normal = CrossProduct(vc-va, vb-va); + normal.Normalise(); + dist = DotProduct(normal, va); + CVector an(fabs(normal.x), fabs(normal.y), fabs(normal.z)); + // find out largest component and its direction + if(an.x > an.y && an.x > an.z) + dir = normal.x < 0.0f ? DIR_X_NEG : DIR_X_POS; + else if(an.y > an.z) + dir = normal.y < 0.0f ? DIR_Y_NEG : DIR_Y_POS; + else + dir = normal.z < 0.0f ? DIR_Z_NEG : DIR_Z_POS; +} + +CColModel::CColModel(void) +{ + numSpheres = 0; + spheres = nil; + numLines = 0; + lines = nil; + numBoxes = 0; + boxes = nil; + numTriangles = 0; + vertices = nil; + triangles = nil; + trianglePlanes = nil; + level = CGame::currLevel; + ownsCollisionVolumes = true; +} + +CColModel::~CColModel(void) +{ + RemoveCollisionVolumes(); + RemoveTrianglePlanes(); +} + +void +CColModel::RemoveCollisionVolumes(void) +{ + if(ownsCollisionVolumes){ + RwFree(spheres); + RwFree(lines); + RwFree(boxes); + RwFree(vertices); + RwFree(triangles); + } + numSpheres = 0; + numLines = 0; + numBoxes = 0; + numTriangles = 0; + spheres = nil; + lines = nil; + boxes = nil; + vertices = nil; + triangles = nil; +} + +void +CColModel::CalculateTrianglePlanes(void) +{ + // HACK: allocate space for one more element to stuff the link pointer into + trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1)); + for(int i = 0; i < numTriangles; i++) + trianglePlanes[i].Set(vertices, triangles[i]); +} + +void +CColModel::RemoveTrianglePlanes(void) +{ + RwFree(trianglePlanes); + trianglePlanes = nil; +} + +void +CColModel::SetLinkPtr(CLink<CColModel*> *lptr) +{ + assert(trianglePlanes); + *(CLink<CColModel*>**)ALIGNPTR(&trianglePlanes[numTriangles]) = lptr; +} + +CLink<CColModel*>* +CColModel::GetLinkPtr(void) +{ + assert(trianglePlanes); + return *(CLink<CColModel*>**)ALIGNPTR(&trianglePlanes[numTriangles]); +} + +void +CColModel::GetTrianglePoint(CVector &v, int i) const +{ + v = vertices[i]; +} + +CColModel& +CColModel::operator=(const CColModel &other) +{ + int i; + int numVerts; + + boundingSphere = other.boundingSphere; + boundingBox = other.boundingBox; + + // copy spheres + if(other.numSpheres){ + if(numSpheres != other.numSpheres){ + numSpheres = other.numSpheres; + if(spheres) + RwFree(spheres); + spheres = (CColSphere*)RwMalloc(numSpheres*sizeof(CColSphere)); + } + for(i = 0; i < numSpheres; i++) + spheres[i] = other.spheres[i]; + }else{ + numSpheres = 0; + if(spheres) + RwFree(spheres); + spheres = nil; + } + + // copy lines + if(other.numLines){ + if(numLines != other.numLines){ + numLines = other.numLines; + if(lines) + RwFree(lines); + lines = (CColLine*)RwMalloc(numLines*sizeof(CColLine)); + } + for(i = 0; i < numLines; i++) + lines[i] = other.lines[i]; + }else{ + numLines = 0; + if(lines) + RwFree(lines); + lines = nil; + } + + // copy boxes + if(other.numBoxes){ + if(numBoxes != other.numBoxes){ + numBoxes = other.numBoxes; + if(boxes) + RwFree(boxes); + boxes = (CColBox*)RwMalloc(numBoxes*sizeof(CColBox)); + } + for(i = 0; i < numBoxes; i++) + boxes[i] = other.boxes[i]; + }else{ + numBoxes = 0; + if(boxes) + RwFree(boxes); + boxes = nil; + } + + // copy mesh + if(other.numTriangles){ + // copy vertices + numVerts = 0; + for(i = 0; i < other.numTriangles; i++){ + if(other.triangles[i].a > numVerts) + other.triangles[i].a = numVerts; + if(other.triangles[i].b > numVerts) + other.triangles[i].b = numVerts; + if(other.triangles[i].c > numVerts) + other.triangles[i].c = numVerts; + } + numVerts++; + if(vertices) + RwFree(vertices); + if(numVerts){ + vertices = (CVector*)RwMalloc(numVerts*sizeof(CVector)); + for(i = 0; i < numVerts; i++) + vertices[i] = other.vertices[i]; + } + + // copy triangles + if(numTriangles != other.numTriangles){ + numTriangles = other.numTriangles; + if(triangles) + RwFree(triangles); + triangles = (CColTriangle*)RwMalloc(numTriangles*sizeof(CColTriangle)); + } + for(i = 0; i < numTriangles; i++) + triangles[i] = other.triangles[i]; + }else{ + numTriangles = 0; + if(triangles) + RwFree(triangles); + triangles = nil; + if(vertices) + RwFree(vertices); + vertices = nil; + } + return *this; +} + +STARTPATCHES + InjectHook(0x4B9C30, (CMatrix& (*)(const CMatrix &src, CMatrix &dst))Invert, PATCH_JUMP); + + InjectHook(0x40B380, CCollision::Init, PATCH_JUMP); + InjectHook(0x40B3A0, CCollision::Shutdown, PATCH_JUMP); + InjectHook(0x40B3B0, CCollision::Update, PATCH_JUMP); + InjectHook(0x40B5B0, CCollision::LoadCollisionWhenINeedIt, PATCH_JUMP); + InjectHook(0x40B900, CCollision::SortOutCollisionAfterLoad, PATCH_JUMP); + + InjectHook(0x40BB70, CCollision::TestSphereBox, PATCH_JUMP); + InjectHook(0x40E130, CCollision::TestLineBox, PATCH_JUMP); + InjectHook(0x40E5C0, CCollision::TestVerticalLineBox, PATCH_JUMP); + InjectHook(0x40EC10, CCollision::TestLineTriangle, PATCH_JUMP); + InjectHook(0x40DAA0, CCollision::TestLineSphere, PATCH_JUMP); + InjectHook(0x40C580, CCollision::TestSphereTriangle, PATCH_JUMP); + InjectHook(0x40F720, CCollision::TestLineOfSight, PATCH_JUMP); + + InjectHook(0x40B9F0, CCollision::ProcessSphereSphere, PATCH_JUMP); + InjectHook(0x40BC00, CCollision::ProcessSphereBox, PATCH_JUMP); + InjectHook(0x40E670, CCollision::ProcessLineBox, PATCH_JUMP); + InjectHook(0x40DE80, CCollision::ProcessLineSphere, PATCH_JUMP); + InjectHook(0x40FB50, CCollision::ProcessVerticalLineTriangle, PATCH_JUMP); + InjectHook(0x40F140, CCollision::ProcessLineTriangle, PATCH_JUMP); + InjectHook(0x40CE30, CCollision::ProcessSphereTriangle, PATCH_JUMP); + + InjectHook(0x40F910, CCollision::ProcessLineOfSight, PATCH_JUMP); + InjectHook(0x410120, CCollision::ProcessVerticalLine, PATCH_JUMP); + InjectHook(0x410BE0, CCollision::ProcessColModels, PATCH_JUMP); + + InjectHook(0x40B960, CCollision::CalculateTrianglePlanes, PATCH_JUMP); + InjectHook(0x411640, &CLink<CColModel*>::Remove, PATCH_JUMP); + InjectHook(0x411620, &CLink<CColModel*>::Insert, PATCH_JUMP); + InjectHook(0x4115C0, &CLinkList<CColModel*>::Insert, PATCH_JUMP); + InjectHook(0x411600, &CLinkList<CColModel*>::Remove, PATCH_JUMP); +// InjectHook(0x411530, &CLinkList<CColModel*>::Init, PATCH_JUMP); + + InjectHook(0x411E40, (void (CColSphere::*)(float, const CVector&, uint8, uint8))&CColSphere::Set, PATCH_JUMP); + InjectHook(0x40B2A0, &CColBox::Set, PATCH_JUMP); + InjectHook(0x40B320, &CColLine::ctor, PATCH_JUMP); + InjectHook(0x40B350, &CColLine::Set, PATCH_JUMP); + InjectHook(0x411E70, &CColTriangle::Set, PATCH_JUMP); + + InjectHook(0x411EA0, &CColTrianglePlane::Set, PATCH_JUMP); + InjectHook(0x412140, &CColTrianglePlane::GetNormal, PATCH_JUMP); + + InjectHook(0x411680, &CColModel::ctor, PATCH_JUMP); + InjectHook(0x4116E0, &CColModel::dtor, PATCH_JUMP); + InjectHook(0x411D80, &CColModel::RemoveCollisionVolumes, PATCH_JUMP); + InjectHook(0x411CB0, &CColModel::CalculateTrianglePlanes, PATCH_JUMP); + InjectHook(0x411D10, &CColModel::RemoveTrianglePlanes, PATCH_JUMP); + InjectHook(0x411D40, &CColModel::SetLinkPtr, PATCH_JUMP); + InjectHook(0x411D60, &CColModel::GetLinkPtr, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Collision.h b/src/core/Collision.h new file mode 100644 index 00000000..5a9058d3 --- /dev/null +++ b/src/core/Collision.h @@ -0,0 +1,157 @@ +#pragma once + +#include "templates.h" +#include "Game.h" // for eLevelName + +struct CColSphere +{ + CVector center; + float radius; + uint8 surface; + uint8 piece; + + void Set(float radius, const CVector ¢er, uint8 surf, uint8 piece); + void Set(float radius, const CVector ¢er) { this->center = center; this->radius = radius; } +}; + +struct CColBox +{ + CVector min; + CVector max; + uint8 surface; + uint8 piece; + + void Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece); + CVector GetSize(void) { return max - min; } +}; + +struct CColLine +{ + CVector p0; + int pad0; + CVector p1; + int pad1; + + CColLine(void) { }; + CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; }; + void Set(const CVector &p0, const CVector &p1); + + CColLine *ctor(CVector *p0, CVector *p1) { return ::new (this) CColLine(*p0, *p1); } +}; + +struct CColTriangle +{ + uint16 a; + uint16 b; + uint16 c; + uint8 surface; + + void Set(const CVector *v, int a, int b, int c, uint8 surf, uint8 piece); +}; + +struct CColTrianglePlane +{ + CVector normal; + float dist; + uint8 dir; + + void Set(const CVector *v, CColTriangle &tri); + void GetNormal(CVector &n) const { n = normal; } + float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; }; +}; + +struct CColPoint +{ + CVector point; + int pad1; + // the surface normal on the surface of point + CVector normal; + int pad2; + uint8 surfaceA; + uint8 pieceA; + uint8 surfaceB; + uint8 pieceB; + float depth; +}; + +struct CStoredCollPoly +{ + CVector verts[3]; + bool valid; +}; + +struct CColModel +{ + CColSphere boundingSphere; + CColBox boundingBox; + short numSpheres; + short numLines; + short numBoxes; + short numTriangles; + int level; + bool ownsCollisionVolumes; + CColSphere *spheres; + CColLine *lines; + CColBox *boxes; + CVector *vertices; + CColTriangle *triangles; + CColTrianglePlane *trianglePlanes; + + CColModel(void); + ~CColModel(void); + void RemoveCollisionVolumes(void); + void CalculateTrianglePlanes(void); + void RemoveTrianglePlanes(void); + CLink<CColModel*> *GetLinkPtr(void); + void SetLinkPtr(CLink<CColModel*>*); + void GetTrianglePoint(CVector &v, int i) const; + + CColModel *ctor(void) { return ::new (this) CColModel(); } + void dtor(void) { this->CColModel::~CColModel(); } + CColModel& operator=(const CColModel& other); +}; + +class CCollision +{ +public: + static eLevelName &ms_collisionInMemory; + static CLinkList<CColModel*> &ms_colModelCache; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void LoadCollisionWhenINeedIt(bool changeLevel); + static void SortOutCollisionAfterLoad(void); + static void LoadCollisionScreen(eLevelName level); + static void DrawColModel(const CMatrix &mat, const CColModel &colModel); + static void DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id); + + static void CalculateTrianglePlanes(CColModel *model); + + // all these return true if there's a collision + static bool TestSphereSphere(const CColSphere &s1, const CColSphere &s2); + static bool TestSphereBox(const CColSphere &sph, const CColBox &box); + static bool TestLineBox(const CColLine &line, const CColBox &box); + static bool TestVerticalLineBox(const CColLine &line, const CColBox &box); + static bool TestLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineSphere(const CColLine &line, const CColSphere &sph); + static bool TestSphereTriangle(const CColSphere &sphere, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough); + + static bool ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq); + static bool ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq); + static bool ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist); + static bool ProcessVerticalLineTriangle(const CColLine &line, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist, CStoredCollPoly *poly); + static bool ProcessLineTriangle(const CColLine &line , const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist); + static bool ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist); + static bool ProcessSphereTriangle(const CColSphere &sph, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindistsq); + static bool ProcessLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough); + static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly); + static int32 ProcessColModels(const CMatrix &matrix1, CColModel &model1, const CMatrix &matrix2, CColModel &model2, CColPoint *point1, CColPoint *point2, float *linedists); + + // TODO: + // CCollision::IsStoredPolyStillValidVerticalLine + + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point); + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest); +}; diff --git a/src/core/ControllerConfig.cpp b/src/core/ControllerConfig.cpp new file mode 100644 index 00000000..d7567ac4 --- /dev/null +++ b/src/core/ControllerConfig.cpp @@ -0,0 +1,57 @@ + #define DIRECTINPUT_VERSION 0x0800 + #include <dinput.h> +#include "common.h" +#include "patcher.h" +#include "ControllerConfig.h" +#include "Pad.h" +#include "FileMgr.h" + +CControllerConfigManager &ControlsManager = *(CControllerConfigManager*)0x8F43A4; + +WRAPPER void CControllerConfigManager::UpdateJoyButtonState(int padnumber) { EAXJMP(0x58F5B0); } +WRAPPER void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonDown(int button, int padnumber) { EAXJMP(0x58C5E0); } +WRAPPER void CControllerConfigManager::AffectControllerStateOn_ButtonDown(int button, eControllerType type) { EAXJMP(0x58C730); } +WRAPPER void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonUp(int button, int padnumber) { EAXJMP(0x58CE80); } +WRAPPER void CControllerConfigManager::AffectControllerStateOn_ButtonUp(int button, int padnumber) { EAXJMP(0x58CFD0); } +WRAPPER void CControllerConfigManager::MakeControllerActionsBlank() { EAXJMP(0x58B7A0); } +WRAPPER void CControllerConfigManager::InitDefaultControlConfiguration() { EAXJMP(0x58B930); } +WRAPPER void CControllerConfigManager::InitDefaultControlConfigMouse(CMouseControllerState const &mousestate) { EAXJMP(0x58BD00); } +WRAPPER int32 CControllerConfigManager::GetJoyButtonJustDown() { EAXJMP(0x58B7D0); } +WRAPPER void CControllerConfigManager::InitDefaultControlConfigJoyPad(unsigned int buttons) { EAXJMP(0x58BD90); } +WRAPPER void CControllerConfigManager::ClearSimButtonPressCheckers() { EAXJMP(0x58D220); } +WRAPPER void CControllerConfigManager::AffectPadFromKeyBoard() { EAXJMP(0x58D0C0); } +WRAPPER void CControllerConfigManager::AffectPadFromMouse() { EAXJMP(0x58D1A0); } + +void CControllerConfigManager::LoadSettings(int32 file) +{ + bool bValid = true; + + if ( file ) + { + char buff[29]; + CFileMgr::Read(file, buff, sizeof(buff)); + + if ( !strncmp(buff, "THIS FILE IS NOT VALID YET", sizeof(buff) - 3) ) + bValid = false; + else + CFileMgr::Seek(file, 0, 0); + } + + if ( bValid ) + { + ControlsManager.MakeControllerActionsBlank(); + + for ( int i = 0; i < 4; i++ ) + { + for ( int j = 0; j < 41; j++ ) + { + CFileMgr::Read(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind)); + } + } + } +} + +WRAPPER void CControllerConfigManager::SaveSettings(int32 file) +{ + EAXJMP(0x58B800); +} diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h new file mode 100644 index 00000000..581efe05 --- /dev/null +++ b/src/core/ControllerConfig.h @@ -0,0 +1,58 @@ +#pragma once + + +// based on x-gtasa + +enum eControllerType +{ + KEYBOARD, + OPTIONAL_EXTRA, + MOUSE, + JOYSTICK, +}; + +class CMouseControllerState; + +class CControllerConfigManager +{ +public: + struct tControllerConfigBind + { + RsKeyCodes m_Key; + int32 m_ContSetOrder; + }; + + bool field_0; + char _pad0[3]; + DIJOYSTATE2 m_OldState; + DIJOYSTATE2 m_NewState; + wchar m_aActionNames[41][40]; + bool m_aButtonStates[17]; + char _pad1[3]; + tControllerConfigBind m_aSettings[41][4]; + uint8 m_aSimCheckers[4][4]; + bool m_bMouseAssociated; + char _pad2[3]; + + void UpdateJoyButtonState(int padnumber); + void UpdateJoyInConfigMenus_ButtonDown(int button, int padnumber); + void AffectControllerStateOn_ButtonDown(int button, eControllerType type); + void UpdateJoyInConfigMenus_ButtonUp(int button, int padnumber); + void AffectControllerStateOn_ButtonUp(int button, int padnumber); + + int32 GetJoyButtonJustDown(); + void LoadSettings(int32 file); + void SaveSettings(int32 file); + void MakeControllerActionsBlank(); + void InitDefaultControlConfiguration(); + void InitDefaultControlConfigMouse(CMouseControllerState const &mousestate); + void InitDefaultControlConfigJoyPad(unsigned int buttons); + void ClearSimButtonPressCheckers(); + void AffectPadFromKeyBoard(); + void AffectPadFromMouse(); + +}; + +VALIDATE_SIZE(CControllerConfigManager, 0x143C); + +extern CControllerConfigManager &ControlsManager;
\ No newline at end of file diff --git a/src/core/CutsceneMgr.cpp b/src/core/CutsceneMgr.cpp new file mode 100644 index 00000000..744ef53d --- /dev/null +++ b/src/core/CutsceneMgr.cpp @@ -0,0 +1,7 @@ +#include "common.h" +#include "patcher.h" +#include "CutsceneMgr.h" + +bool &CCutsceneMgr::ms_running = *(bool*)0x95CCF5; +bool &CCutsceneMgr::ms_cutsceneProcessing = *(bool*)0x95CD9F; +CDirectory *&CCutsceneMgr::ms_pCutsceneDir = *(CDirectory**)0x8F5F88; diff --git a/src/core/CutsceneMgr.h b/src/core/CutsceneMgr.h new file mode 100644 index 00000000..89f6ab8d --- /dev/null +++ b/src/core/CutsceneMgr.h @@ -0,0 +1,15 @@ +#pragma once + +class CDirectory; + +class CCutsceneMgr +{ + static bool &ms_running; + static bool &ms_cutsceneProcessing; + +public: + static CDirectory *&ms_pCutsceneDir; + + static bool IsRunning(void) { return ms_running; } + static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; } +}; diff --git a/src/core/Directory.cpp b/src/core/Directory.cpp new file mode 100644 index 00000000..3e0d5382 --- /dev/null +++ b/src/core/Directory.cpp @@ -0,0 +1,65 @@ +#include "common.h" +#include "patcher.h" +#include "FileMgr.h" +#include "Directory.h" + +CDirectory::CDirectory(int32 maxEntries) + : numEntries(0), maxEntries(maxEntries) +{ + entries = new DirectoryInfo[maxEntries]; +} + +CDirectory::~CDirectory(void) +{ + delete[] entries; +} + +void +CDirectory::ReadDirFile(const char *filename) +{ + int fd; + DirectoryInfo dirinfo; + + fd = CFileMgr::OpenFile(filename, "rb"); + while(CFileMgr::Read(fd, (char*)&dirinfo, sizeof(dirinfo))) + AddItem(dirinfo); + CFileMgr::CloseFile(fd); +} + +bool +CDirectory::WriteDirFile(const char *filename) +{ + int fd, n; + fd = CFileMgr::OpenFileForWriting(filename); + n = CFileMgr::Write(fd, (char*)entries, numEntries*sizeof(DirectoryInfo)); + CFileMgr::CloseFile(fd); + return n == numEntries*sizeof(DirectoryInfo); +} + +void +CDirectory::AddItem(const DirectoryInfo &dirinfo) +{ + assert(numEntries < maxEntries); + entries[numEntries++] = dirinfo; +} + +bool +CDirectory::FindItem(const char *name, uint32 &offset, uint32 &size) +{ + int i; + + for(i = 0; i < numEntries; i++) + if(strcmpi(entries[i].name, name) == 0){ + offset = entries[i].offset; + size = entries[i].size; + return true; + } + return false; +} + +STARTPATCHES + InjectHook(0x473630, &CDirectory::ReadDirFile, PATCH_JUMP); + InjectHook(0x473690, &CDirectory::WriteDirFile, PATCH_JUMP); + InjectHook(0x473600, &CDirectory::AddItem, PATCH_JUMP); + InjectHook(0x4736E0, &CDirectory::FindItem, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Directory.h b/src/core/Directory.h new file mode 100644 index 00000000..06e6bba4 --- /dev/null +++ b/src/core/Directory.h @@ -0,0 +1,22 @@ +#pragma once + +class CDirectory +{ +public: + struct DirectoryInfo { + uint32 offset; + uint32 size; + char name[24]; + }; + DirectoryInfo *entries; + int32 maxEntries; + int32 numEntries; + + CDirectory(int32 maxEntries); + ~CDirectory(void); + + void ReadDirFile(const char *filename); + bool WriteDirFile(const char *filename); + void AddItem(const DirectoryInfo &dirinfo); + bool FindItem(const char *name, uint32 &offset, uint32 &size); +}; diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp new file mode 100644 index 00000000..fdc3b9d7 --- /dev/null +++ b/src/core/FileLoader.cpp @@ -0,0 +1,1182 @@ +#include "common.h" +#include "main.h" +#include "patcher.h" +#include "math/Quaternion.h" +#include "ModelInfo.h" +#include "ModelIndices.h" +#include "TempColModels.h" +#include "VisibilityPlugins.h" +#include "FileMgr.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "PedType.h" +#include "PedStats.h" +#include "AnimManager.h" +#include "Game.h" +#include "RwHelper.h" +#include "NodeName.h" +#include "TxdStore.h" +#include "PathFind.h" +#include "ObjectData.h" +#include "DummyObject.h" +#include "World.h" +#include "Zones.h" +#include "ZoneCull.h" +#include "CdStream.h" +#include "FileLoader.h" + +char CFileLoader::ms_line[256]; + +const char* +GetFilename(const char *filename) +{ + char *s = strrchr((char*)filename, '\\'); + return s ? s+1 : filename; +} + +void +LoadingScreenLoadingFile(const char *filename) +{ + sprintf(gString, "Loading %s", GetFilename(filename)); + LoadingScreen("Loading the Game", gString, nil); +} + +void +CFileLoader::LoadLevel(const char *filename) +{ + int fd; + RwTexDictionary *savedTxd; + eLevelName savedLevel; + bool objectsLoaded; + char *line; + char txdname[64]; + + savedTxd = RwTexDictionaryGetCurrent(); + objectsLoaded = false; + savedLevel = CGame::currLevel; + if(savedTxd == nil){ + savedTxd = RwTexDictionaryCreate(); + RwTexDictionarySetCurrent(savedTxd); + } + fd = CFileMgr::OpenFile(filename, "r"); + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + + if(strncmp(line, "EXIT", 9) == 0) // BUG: 9? + break; + + if(strncmp(line, "IMAGEPATH", 9) == 0){ + RwImageSetPath(line + 10); + }else if(strncmp(line, "TEXDICTION", 10) == 0){ + strcpy(txdname, line+11); + LoadingScreenLoadingFile(txdname); + RwTexDictionary *txd = LoadTexDictionary(txdname); + AddTexDictionaries(savedTxd, txd); + RwTexDictionaryDestroy(txd); + }else if(strncmp(line, "COLFILE", 7) == 0){ + int level; + sscanf(line+8, "%d", &level); + CGame::currLevel = (eLevelName)level; + LoadingScreenLoadingFile(line+10); + LoadCollisionFile(line+10); + CGame::currLevel = savedLevel; + }else if(strncmp(line, "MODELFILE", 9) == 0){ + LoadingScreenLoadingFile(line + 10); + LoadModelFile(line + 10); + }else if(strncmp(line, "HIERFILE", 8) == 0){ + LoadingScreenLoadingFile(line + 9); + LoadClumpFile(line + 9); + }else if(strncmp(line, "IDE", 3) == 0){ + LoadingScreenLoadingFile(line + 4); + LoadObjectTypes(line + 4); + }else if(strncmp(line, "IPL", 3) == 0){ + if(!objectsLoaded){ + // CModelInfo::ConstructMloClumps(); + CObjectData::Initialise("DATA\\OBJECT.DAT"); + objectsLoaded = true; + } + LoadingScreenLoadingFile(line + 4); + LoadScene(line + 4); + }else if(strncmp(line, "MAPZONE", 7) == 0){ + LoadingScreenLoadingFile(line + 8); + LoadMapZones(line + 8); + }else if(strncmp(line, "SPLASH", 6) == 0){ + LoadSplash(GetRandomSplashScreen()); + }else if(strncmp(line, "CDIMAGE", 7) == 0){ + CdStreamAddImage(line + 8); + } + } + + CFileMgr::CloseFile(fd); + RwTexDictionarySetCurrent(savedTxd); +} + +void +CFileLoader::LoadCollisionFromDatFile(int currlevel) +{ + int fd; + char *line; + + fd = CFileMgr::OpenFile(CGame::aDatFile, "r"); + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + + if(strncmp(line, "COLFILE", 7) == 0){ + int level; + sscanf(line+8, "%d", &level); + if(currlevel == level) + LoadCollisionFile(line+10); + } + } + + CFileMgr::CloseFile(fd); +} + +char* +CFileLoader::LoadLine(int fd) +{ + int i; + char *line; + + if(CFileMgr::ReadLine(fd, ms_line, 256) == false) + return nil; + for(i = 0; ms_line[i] != '\0'; i++) + if(ms_line[i] < ' ' || ms_line[i] == ',') + ms_line[i] = ' '; + for(line = ms_line; *line <= ' ' && *line != '\0'; line++); + return line; +} + +RwTexDictionary* +CFileLoader::LoadTexDictionary(const char *filename) +{ + RwTexDictionary *txd; + RwStream *stream; + + txd = nil; + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + debug("Loading texture dictionary file %s\n", filename); + if(stream){ + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)) + txd = RwTexDictionaryGtaStreamRead(stream); + RwStreamClose(stream, nil); + } + if(txd == nil) + txd = RwTexDictionaryCreate(); + return txd; +} + +void +CFileLoader::LoadCollisionFile(const char *filename) +{ + int fd; + char modelname[24]; + CBaseModelInfo *mi; + struct { + char ident[4]; + uint32 size; + } header; + + debug("Loading collision file %s\n", filename); + fd = CFileMgr::OpenFile(filename, "rb"); + + while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){ + assert(strncmp(header.ident, "COLL", 4) == 0); + CFileMgr::Read(fd, (char*)work_buff, header.size); + memcpy(modelname, work_buff, 24); + + mi = CModelInfo::GetModelInfo(modelname, nil); + if(mi){ + if(mi->GetColModel()){ + LoadCollisionModel(work_buff+24, *mi->GetColModel(), modelname); + }else{ + CColModel *model = new CColModel; + LoadCollisionModel(work_buff+24, *model, modelname); + mi->SetColModel(model, true); + } + }else{ + debug("colmodel %s can't find a modelinfo\n", modelname); + } + } + + CFileMgr::CloseFile(fd); +} + +void +CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname) +{ + int i; + + model.boundingSphere.radius = *(float*)(buf); + model.boundingSphere.center.x = *(float*)(buf+4); + model.boundingSphere.center.y = *(float*)(buf+8); + model.boundingSphere.center.z = *(float*)(buf+12); + model.boundingBox.min.x = *(float*)(buf+16); + model.boundingBox.min.y = *(float*)(buf+20); + model.boundingBox.min.z = *(float*)(buf+24); + model.boundingBox.max.x = *(float*)(buf+28); + model.boundingBox.max.y = *(float*)(buf+32); + model.boundingBox.max.z = *(float*)(buf+36); + model.numSpheres = *(int16*)(buf+40); + buf += 44; + if(model.numSpheres > 0){ + model.spheres = (CColSphere*)RwMalloc(model.numSpheres*sizeof(CColSphere)); + for(i = 0; i < model.numSpheres; i++){ + model.spheres[i].Set(*(float*)buf, *(CVector*)(buf+4), buf[16], buf[17]); + buf += 20; + } + }else + model.spheres = nil; + + model.numLines = *(int16*)buf; + buf += 4; + if(model.numLines > 0){ + model.lines = (CColLine*)RwMalloc(model.numLines*sizeof(CColLine)); + for(i = 0; i < model.numLines; i++){ + model.lines[i].Set(*(CVector*)buf, *(CVector*)(buf+12)); + buf += 24; + } + }else + model.lines = nil; + + model.numBoxes = *(int16*)buf; + buf += 4; + if(model.numBoxes > 0){ + model.boxes = (CColBox*)RwMalloc(model.numBoxes*sizeof(CColBox)); + for(i = 0; i < model.numBoxes; i++){ + model.boxes[i].Set(*(CVector*)buf, *(CVector*)(buf+12), buf[24], buf[25]); + buf += 28; + } + }else + model.boxes = nil; + + int32 numVertices = *(int16*)buf; + buf += 4; + if(numVertices > 0){ + model.vertices = (CVector*)RwMalloc(numVertices*sizeof(CVector)); + for(i = 0; i < numVertices; i++){ + model.vertices[i] = *(CVector*)buf; + if(fabs(model.vertices[i].x) >= 256.0f || + fabs(model.vertices[i].y) >= 256.0f || + fabs(model.vertices[i].z) >= 256.0f) + printf("%s:Collision volume too big\n", modelname); + buf += 12; + } + }else + model.vertices = nil; + + model.numTriangles = *(int16*)buf; + buf += 4; + if(model.numTriangles > 0){ + model.triangles = (CColTriangle*)RwMalloc(model.numTriangles*sizeof(CColTriangle)); + for(i = 0; i < model.numTriangles; i++){ + model.triangles[i].Set(model.vertices, *(int32*)buf, *(int32*)(buf+4), *(int32*)(buf+8), buf[12], buf[13]); + buf += 16; + } + }else + model.triangles = nil; +} + +static void +GetNameAndLOD(char *nodename, char *name, int *n) +{ + char *underscore = nil; + for(char *s = nodename; *s != '\0'; s++){ + if(s[0] == '_' && (s[1] == 'l' || s[1] == 'L')) + underscore = s; + } + if(underscore){ + strncpy(name, nodename, underscore - nodename); + name[underscore - nodename] = '\0'; + *n = atoi(underscore + 2); + }else{ + strncpy(name, nodename, 24); + *n = 0; + } +} + +RpAtomic* +CFileLoader::FindRelatedModelInfoCB(RpAtomic *atomic, void *data) +{ + CSimpleModelInfo *mi; + char *nodename, name[24]; + int n; + RpClump *clump = (RpClump*)data; + + nodename = GetFrameNodeName(RpClumpGetFrame(atomic)); + GetNameAndLOD(nodename, name, &n); + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(name, nil); + if(mi){ + assert(mi->IsSimple()); + mi->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, mi); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + }else{ + debug("Can't find Atomic %s\n", name); + } + + return atomic; +} + +void +CFileLoader::LoadModelFile(const char *filename) +{ + RwStream *stream; + RpClump *clump; + + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump){ + RpClumpForAllAtomics(clump, FindRelatedModelInfoCB, clump); + RpClumpDestroy(clump); + } + } + RwStreamClose(stream, nil); +} + +void +CFileLoader::LoadClumpFile(const char *filename) +{ + RwStream *stream; + RpClump *clump; + char *nodename, name[24]; + int n; + CClumpModelInfo *mi; + + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + while(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump){ + nodename = GetFrameNodeName(RpClumpGetFrame(clump)); + GetNameAndLOD(nodename, name, &n); + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(name, nil); + assert(mi->IsClump()); + if(mi) + mi->SetClump(clump); + else + RpClumpDestroy(clump); + } + } + RwStreamClose(stream, nil); +} + +bool +CFileLoader::LoadClumpFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + CClumpModelInfo *mi; + + if(!RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + return false; + clump = RpClumpStreamRead(stream); + if(clump == nil) + return false; + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id); + mi->SetClump(clump); + if(mi->m_type == MITYPE_PED && id != 0 && RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + // Read LOD ped + clump = RpClumpStreamRead(stream); + if(clump){ + ((CPedModelInfo*)mi)->SetLowDetailClump(clump); + RpClumpDestroy(clump); + } + } + return true; +} + +bool +CFileLoader::StartLoadClumpFile(RwStream *stream, uint32 id) +{ + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + printf("Start loading %s\n", CModelInfo::GetModelInfo(id)->GetName()); + return RpClumpGtaStreamRead1(stream); + }else{ + printf("FAILED\n"); + return false; + } +} + +bool +CFileLoader::FinishLoadClumpFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + CClumpModelInfo *mi; + + printf("Finish loading %s\n", CModelInfo::GetModelInfo(id)->GetName()); + clump = RpClumpGtaStreamRead2(stream); + + if(clump){ + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id); + mi->SetClump(clump); + return true; + }else{ + printf("FAILED\n"); + return false; + } +} + +CSimpleModelInfo *gpRelatedModelInfo; + +bool +CFileLoader::LoadAtomicFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump == nil) + return false; + gpRelatedModelInfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + RpClumpForAllAtomics(clump, SetRelatedModelInfoCB, clump); + RpClumpDestroy(clump); + } + return true; +} + +RpAtomic* +CFileLoader::SetRelatedModelInfoCB(RpAtomic *atomic, void *data) +{ + char *nodename, name[24]; + int n; + RpClump *clump = (RpClump*)data; + + nodename = GetFrameNodeName(RpAtomicGetFrame(atomic)); + GetNameAndLOD(nodename, name, &n); + gpRelatedModelInfo->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, gpRelatedModelInfo); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +RpClump* +CFileLoader::LoadAtomicFile2Return(const char *filename) +{ + RwStream *stream; + RpClump *clump; + + clump = nil; + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + clump = RpClumpStreamRead(stream); + RwStreamClose(stream, nil); + return clump; +} + +static RwTexture* +MoveTexturesCB(RwTexture *texture, void *pData) +{ + RwTexDictionaryAddTexture((RwTexDictionary*)pData, texture); + return texture; +} + +void +CFileLoader::AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src) +{ + RwTexDictionaryForAllTextures(src, MoveTexturesCB, dst); +} + +void +CFileLoader::LoadObjectTypes(const char *filename) +{ + enum { + NONE, + OBJS, + MLO, + TOBJ, + HIER, + CARS, + PEDS, + PATH, + TWODFX + }; + char *line; + int fd; + int section; + int pathIndex; + char pathTypeStr[20]; + int id, pathType; +// int mlo; + + section = NONE; + pathIndex = -1; +// mlo = 0; + debug("Loading object types from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(strncmp(line, "objs", 4) == 0) section = OBJS; + else if(strncmp(line, "tobj", 4) == 0) section = TOBJ; + else if(strncmp(line, "hier", 4) == 0) section = HIER; + else if(strncmp(line, "cars", 4) == 0) section = CARS; + else if(strncmp(line, "peds", 4) == 0) section = PEDS; + else if(strncmp(line, "path", 4) == 0) section = PATH; + else if(strncmp(line, "2dfx", 4) == 0) section = TWODFX; + }else if(strncmp(line, "end", 3) == 0){ + section = section == MLO ? OBJS : NONE; + }else switch(section){ + case OBJS: + if(strncmp(line, "sta", 3) == 0) + assert(0); // LoadMLO + else + LoadObject(line); + break; + case MLO: + assert(0); // LoadMLOInstance + break; + case TOBJ: + LoadTimeObject(line); + break; + case HIER: + LoadClumpObject(line); + break; + case CARS: + LoadVehicleObject(line); + break; + case PEDS: + LoadPedObject(line); + break; + case PATH: + if(pathIndex == -1){ + id = LoadPathHeader(line, pathTypeStr); + if(strncmp(pathTypeStr, "ped", 4) == 0) + pathType = 1; + else if(strncmp(pathTypeStr, "car", 4) == 0) + pathType = 0; + pathIndex = 0; + }else{ + if(pathType == 1) + LoadPedPathNode(line, id, pathIndex); + else if(pathType == 0) + LoadCarPathNode(line, id, pathIndex); + pathIndex++; + if(pathIndex == 12) + pathIndex = -1; + } + break; + case TWODFX: + Load2dEffect(line); + break; + } + } + CFileMgr::CloseFile(fd); + + for(id = 0; id < MODELINFOSIZE; id++){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi && mi->IsSimple()) + mi->SetupBigBuilding(); + } +} + +void +SetModelInfoFlags(CSimpleModelInfo *mi, uint32 flags) +{ + mi->m_normalCull = !!(flags & 1); + mi->m_noFade = !!(flags & 2); + mi->m_drawLast = !!(flags & (4|8)); + mi->m_additive = !!(flags & 8); + mi->m_isSubway = !!(flags & 0x10); + mi->m_ignoreLight = !!(flags & 0x20); + mi->m_noZwrite = !!(flags & 0x40); +} + +void +CFileLoader::LoadObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + int damaged; + CSimpleModelInfo *mi; + + if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4) + return; + + switch(numObjs){ + case 1: + sscanf(line, "%d %s %s %d %f %d", + &id, model, txd, &numObjs, &dist[0], &flags); + damaged = 0; + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags); + damaged = dist[0] < dist[1] ? // Are distances increasing? + 0 : // Yes, no damage model + 1; // No, 1 is damaged + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags); + damaged = dist[0] < dist[1] ? // Are distances increasing? + (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model + 1; // No, 1 and 2 are damaged + break; + } + + mi = CModelInfo::AddSimpleModel(id); + mi->SetName(model); + mi->SetNumAtomics(numObjs); + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + mi->m_firstDamaged = damaged; + mi->SetTexDictionary(txd); + MatchModelString(model, id); +} + +void +CFileLoader::LoadTimeObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + int timeOn, timeOff; + int damaged; + CTimeModelInfo *mi, *other; + + if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4) + return; + + switch(numObjs){ + case 1: + sscanf(line, "%d %s %s %d %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &flags, &timeOn, &timeOff); + damaged = 0; + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags, &timeOn, &timeOff); + damaged = dist[0] < dist[1] ? // Are distances increasing? + 0 : // Yes, no damage model + 1; // No, 1 is damaged + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags, &timeOn, &timeOff); + damaged = dist[0] < dist[1] ? // Are distances increasing? + (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model + 1; // No, 1 and 2 are damaged + break; + } + + mi = CModelInfo::AddTimeModel(id); + mi->SetName(model); + mi->SetNumAtomics(numObjs); + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + mi->m_firstDamaged = damaged; + mi->SetTimes(timeOn, timeOff); + mi->SetTexDictionary(txd); + other = mi->FindOtherTimeModel(); + if(other) + other->SetOtherTimeModel(id); + MatchModelString(model, id); +} + +void +CFileLoader::LoadClumpObject(const char *line) +{ + int id; + char model[24], txd[24]; + CClumpModelInfo *mi; + + if(sscanf(line, "%d %s %s", &id, &model, &txd) == 3){ + mi = CModelInfo::AddClumpModel(id); + mi->SetName(model); + mi->SetTexDictionary(txd); + mi->SetColModel(&CTempColModels::ms_colModelBBox); + } +} + +void +CFileLoader::LoadVehicleObject(const char *line) +{ + int id; + char model[24], txd[24]; + char type[8], handlingId[16], gamename[32], vehclass[12]; + uint32 frequency, comprules; + int32 level, misc; + float wheelScale; + CVehicleModelInfo *mi; + char *p; + + sscanf(line, "%d %s %s %s %s %s %s %d %d %x %d %f", + &id, model, txd, + type, handlingId, gamename, vehclass, + &frequency, &level, &comprules, &misc, &wheelScale); + + mi = CModelInfo::AddVehicleModel(id); + mi->SetName(model); + mi->SetTexDictionary(txd); + for(p = gamename; *p; p++) + if(*p == '_') *p = ' '; + strncpy(mi->m_gameName, gamename, 32); + mi->m_level = level; + mi->m_compRules = comprules; + + if(strncmp(type, "car", 4) == 0){ + mi->m_wheelId = misc; + mi->m_wheelScale = wheelScale; + mi->m_vehicleType = VEHICLE_TYPE_CAR; + }else if(strncmp(type, "boat", 5) == 0){ + mi->m_vehicleType = VEHICLE_TYPE_BOAT; + }else if(strncmp(type, "train", 6) == 0){ + mi->m_vehicleType = VEHICLE_TYPE_TRAIN; + }else if(strncmp(type, "heli", 5) == 0){ + mi->m_vehicleType = VEHICLE_TYPE_HELI; + }else if(strncmp(type, "plane", 6) == 0){ + mi->m_wheelId = misc; + mi->m_wheelScale = 1.0f; + mi->m_vehicleType = VEHICLE_TYPE_PLANE; + }else if(strncmp(type, "bike", 5) == 0){ + mi->m_bikeSteerAngle = misc; + mi->m_wheelScale = wheelScale; + mi->m_vehicleType = VEHICLE_TYPE_BIKE; + }else + assert(0); + + mi->m_handlingId = mod_HandlingManager.GetHandlingId(handlingId); + + // Well this is kinda dumb.... + if(strncmp(vehclass, "poorfamily", 11) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_POOR; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_POOR); + }else if(strncmp(vehclass, "richfamily", 11) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_RICH; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_RICH); + }else if(strncmp(vehclass, "executive", 10) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_EXECUTIVE; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_EXECUTIVE); + }else if(strncmp(vehclass, "worker", 7) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_WORKER; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_WORKER); + }else if(strncmp(vehclass, "special", 8) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_SPECIAL; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_SPECIAL); + }else if(strncmp(vehclass, "big", 4) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_BIG; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_BIG); + }else if(strncmp(vehclass, "taxi", 5) == 0){ + mi->m_vehicleClass = VEHICLE_CLASS_TAXI; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, VEHICLE_CLASS_TAXI); + } +} + +void +CFileLoader::LoadPedObject(const char *line) +{ + int id; + char model[24], txd[24]; + char pedType[24], pedStats[24], animGroup[24]; + int carsCanDrive; + CPedModelInfo *mi; + int animGroupId; + + if(sscanf(line, "%d %s %s %s %s %s %x", + &id, model, txd, + pedType, pedStats, animGroup, &carsCanDrive) != 7) + return; + + mi = CModelInfo::AddPedModel(id); + mi->SetName(model); + mi->SetTexDictionary(txd); + mi->SetColModel(&CTempColModels::ms_colModelPed1); + mi->m_pedType = CPedType::FindPedType(pedType); + mi->m_pedStatType = CPedStats::GetPedStatType(pedStats); + for(animGroupId = 0; animGroupId < NUM_ANIM_ASSOC_GROUPS; animGroupId++) + if(strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)animGroupId)) == 0) + break; + mi->m_animGroup = animGroupId; + + // ??? + CModelInfo::GetModelInfo(MI_LOPOLYGUY)->SetColModel(&CTempColModels::ms_colModelPed1); +} + +int +CFileLoader::LoadPathHeader(const char *line, char *type) +{ + int id; + char modelname[32]; + + sscanf(line, "%s %d %s", type, &id, modelname); + return id; +} + +void +CFileLoader::LoadPedPathNode(const char *line, int id, int node) +{ + int type, next, cross; + float x, y, z, width; + + sscanf(line, "%d %d %d %f %f %f %f", &type, &next, &cross, &x, &y, &z, &width); + ThePaths.StoreNodeInfoPed(id, node, type, next, x, y, z, 0, !!cross); +} + +void +CFileLoader::LoadCarPathNode(const char *line, int id, int node) +{ + int type, next, cross, numLeft, numRight; + float x, y, z, width; + + sscanf(line, "%d %d %d %f %f %f %f %d %d", &type, &next, &cross, &x, &y, &z, &width, &numLeft, &numRight); + ThePaths.StoreNodeInfoCar(id, node, type, next, x, y, z, 0, numLeft, numRight); +} + + +void +CFileLoader::Load2dEffect(const char *line) +{ + int id, r, g, b, a, type; + float x, y, z; + char corona[32], shadow[32]; + int shadowIntens, lightType, roadReflection, flare, flags, probability; + CBaseModelInfo *mi; + C2dEffect *effect; + char *p; + + sscanf(line, "%d %f %f %f %d %d %d %d %d", &id, &x, &y, &z, &r, &g, &b, &a, &type); + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + + mi = CModelInfo::GetModelInfo(id); + effect = CModelInfo::Get2dEffectStore().alloc(); + mi->Add2dEffect(effect); + effect->pos = CVector(x, y, z); + effect->col = CRGBA(r, g, b, a); + effect->type = type; + + switch(effect->type){ + case EFFECT_LIGHT: + while(*line++ != '"'); + p = corona; + while(*line != '"') *p++ = *line++; + *p = '\0'; + line++; + + while(*line++ != '"'); + p = shadow; + while(*line != '"') *p++ = *line++; + *p = '\0'; + line++; + + sscanf(line, "%f %f %f %f %d %d %d %d %d", + &effect->light.dist, + &effect->light.range, + &effect->light.size, + &effect->light.shadowRange, + &shadowIntens, &lightType, &roadReflection, &flare, &flags); + effect->light.corona = RwTextureRead(corona, nil); + effect->light.shadow = RwTextureRead(shadow, nil); + effect->light.shadowIntensity = shadowIntens; + effect->light.lightType = lightType; + effect->light.roadReflection = roadReflection; + effect->light.flareType = flare; + // TODO: check out the flags + if(flags & 4) + flags &= ~2; + effect->light.flags = flags; + break; + + case EFFECT_PARTICLE: + sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %f", + &id, &x, &y, &z, &r, &g, &b, &a, &type, + &effect->particle.particleType, + &effect->particle.dir.x, + &effect->particle.dir.y, + &effect->particle.dir.z, + &effect->particle.scale); + break; + + case EFFECT_ATTRACTOR: + sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %d", + &id, &x, &y, &z, &r, &g, &b, &a, &type, + &flags, + &effect->attractor.dir.x, + &effect->attractor.dir.y, + &effect->attractor.dir.z, + &probability); + effect->attractor.flags = flags; + effect->attractor.probability = probability; + break; + } + + CTxdStore::PopCurrentTxd(); +} + +void +CFileLoader::LoadScene(const char *filename) +{ + enum { + NONE, + INST, + ZONE, + CULL, + PICK, + PATH, + }; + char *line; + int fd; + int section; + int pathIndex; + char pathTypeStr[20]; + + section = NONE; + pathIndex = -1; + debug("Creating objects from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(strncmp(line, "inst", 4) == 0) section = INST; + else if(strncmp(line, "zone", 4) == 0) section = ZONE; + else if(strncmp(line, "cull", 4) == 0) section = CULL; + else if(strncmp(line, "pick", 4) == 0) section = PICK; + else if(strncmp(line, "path", 4) == 0) section = PATH; + }else if(strncmp(line, "end", 3) == 0){ + section = NONE; + }else switch(section){ + case INST: + LoadObjectInstance(line); + break; + case ZONE: + LoadZone(line); + break; + case CULL: + LoadCullZone(line); + break; + case PICK: + // unused + LoadPickup(line); + break; + case PATH: + // unfinished in the game + if(pathIndex == -1){ + LoadPathHeader(line, pathTypeStr); + // type not set + pathIndex = 0; + }else{ + // nodes not loaded + pathIndex++; + if(pathIndex == 12) + pathIndex = -1; + } + break; + } + } + CFileMgr::CloseFile(fd); + + debug("Finished loading IPL\n"); +} + +void +CFileLoader::LoadObjectInstance(const char *line) +{ + int id; + char name[24]; + RwV3d trans, scale, axis; + float angle; + CSimpleModelInfo *mi; + RwMatrix *xform; + CEntity *entity; + + if(sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f", + &id, name, + &trans.x, &trans.y, &trans.z, + &scale.x, &scale.y, &scale.z, + &axis.x, &axis.y, &axis.z, &angle) != 12) + return; + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi == nil) + return; + assert(mi->IsSimple()); + + angle = -RADTODEG(2.0f * acosf(angle)); + xform = RwMatrixCreate(); + RwMatrixRotate(xform, &axis, angle, rwCOMBINEREPLACE); + RwMatrixTranslate(xform, &trans, rwCOMBINEPOSTCONCAT); + + if(mi->GetObjectID() == -1){ + if(ThePaths.IsPathObject(id)){ + entity = new CTreadable; + ThePaths.RegisterMapObject((CTreadable*)entity); + }else + entity = new CBuilding; + entity->SetModelIndexNoCreate(id); + entity->GetMatrix() = CMatrix(xform); + entity->m_level = CTheZones::GetLevelFromPosition(entity->GetPosition()); + if(mi->IsSimple()){ + if(mi->m_isBigBuilding) + entity->SetupBigBuilding(); + if(mi->m_isSubway) + entity->bIsSubway = true; + } + if(mi->GetLargestLodDistance() < 2.0f) + entity->bIsVisible = false; + CWorld::Add(entity); + }else{ + entity = new CDummyObject; + entity->SetModelIndexNoCreate(id); + entity->GetMatrix() = CMatrix(xform); + CWorld::Add(entity); + if(IsGlass(entity->GetModelIndex())) + entity->bIsVisible = false; + entity->m_level = CTheZones::GetLevelFromPosition(entity->GetPosition()); + } + + RwMatrixDestroy(xform); +} + +void +CFileLoader::LoadZone(const char *line) +{ + char name[24]; + int type, level; + float minx, miny, minz; + float maxx, maxy, maxz; + + if(sscanf(line, "%s %d %f %f %f %f %f %f %d", name, &type, &minx, &miny, &minz, &maxx, &maxy, &maxz, &level) == 9) + CTheZones::CreateZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level); +} + +void +CFileLoader::LoadCullZone(const char *line) +{ + CVector pos; + float minx, miny, minz; + float maxx, maxy, maxz; + int flags; + int wantedLevelDrop = 0; + + sscanf(line, "%f %f %f %f %f %f %f %f %f %d %d", + &pos.x, &pos.y, &pos.z, + &minx, &miny, &minz, + &maxx, &maxy, &maxz, + &flags, &wantedLevelDrop); + CCullZones::AddCullZone(pos, minx, maxx, miny, maxy, minz, maxz, flags, wantedLevelDrop); +} + +// unused +void +CFileLoader::LoadPickup(const char *line) +{ + int id; + float x, y, z; + + sscanf(line, "%d %f %f %f", &id, &x, &y, &z); +} + +void +CFileLoader::LoadMapZones(const char *filename) +{ + enum { + NONE, + INST, + ZONE, + CULL, + PICK, + PATH, + }; + char *line; + int fd; + int section; + + section = NONE; + debug("Creating zones from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(strncmp(line, "zone", 4) == 0) section = ZONE; + }else if(strncmp(line, "end", 3) == 0){ + section = NONE; + }else switch(section){ + case ZONE: { + char name[24]; + int type, level; + float minx, miny, minz; + float maxx, maxy, maxz; + if(sscanf(line, "%s %d %f %f %f %f %f %f %d", + &name, &type, + &minx, &miny, &minz, + &maxx, &maxy, &maxz, + &level) == 9) + CTheZones::CreateMapZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level); + } + break; + } + } + CFileMgr::CloseFile(fd); + + debug("Finished loading IPL\n"); +} + + +STARTPATCHES + InjectHook(0x476290, CFileLoader::LoadLevel, PATCH_JUMP); + + InjectHook(0x476520, CFileLoader::LoadCollisionFromDatFile, PATCH_JUMP); + InjectHook(0x4761D0, CFileLoader::LoadLine, PATCH_JUMP); + InjectHook(0x4765B0, CFileLoader::LoadTexDictionary, PATCH_JUMP); + InjectHook(0x478B20, CFileLoader::LoadCollisionFile, PATCH_JUMP); + InjectHook(0x478C20, CFileLoader::LoadCollisionModel, PATCH_JUMP); + InjectHook(0x476750, CFileLoader::LoadModelFile, PATCH_JUMP); + InjectHook(0x476810, (void (*)(const char*))CFileLoader::LoadClumpFile, PATCH_JUMP); + InjectHook(0x476990, (bool (*)(RwStream*,uint32))CFileLoader::LoadClumpFile, PATCH_JUMP); + InjectHook(0x476A20, CFileLoader::StartLoadClumpFile, PATCH_JUMP); + InjectHook(0x476A70, CFileLoader::FinishLoadClumpFile, PATCH_JUMP); + InjectHook(0x476930, CFileLoader::LoadAtomicFile, PATCH_JUMP); + InjectHook(0x4767C0, CFileLoader::LoadAtomicFile2Return, PATCH_JUMP); + InjectHook(0x476630, CFileLoader::AddTexDictionaries, PATCH_JUMP); + + InjectHook(0x476AC0, CFileLoader::LoadObjectTypes, PATCH_JUMP); + InjectHook(0x477040, CFileLoader::LoadObject, PATCH_JUMP); + InjectHook(0x4774B0, CFileLoader::LoadTimeObject, PATCH_JUMP); + InjectHook(0x477920, CFileLoader::LoadClumpObject, PATCH_JUMP); + InjectHook(0x477990, CFileLoader::LoadVehicleObject, PATCH_JUMP); + InjectHook(0x477DE0, CFileLoader::LoadPedObject, PATCH_JUMP); + InjectHook(0x477ED0, CFileLoader::LoadPathHeader, PATCH_JUMP); + InjectHook(0x477FF0, CFileLoader::LoadCarPathNode, PATCH_JUMP); + InjectHook(0x477F00, CFileLoader::LoadPedPathNode, PATCH_JUMP); + InjectHook(0x4780E0, CFileLoader::Load2dEffect, PATCH_JUMP); + + InjectHook(0x478370, CFileLoader::LoadScene, PATCH_JUMP); + InjectHook(0x4786B0, CFileLoader::LoadObjectInstance, PATCH_JUMP); + InjectHook(0x478A00, CFileLoader::LoadZone, PATCH_JUMP); + InjectHook(0x478A90, CFileLoader::LoadCullZone, PATCH_JUMP); + + InjectHook(0x478550, CFileLoader::LoadMapZones, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/FileLoader.h b/src/core/FileLoader.h new file mode 100644 index 00000000..f9121ace --- /dev/null +++ b/src/core/FileLoader.h @@ -0,0 +1,42 @@ +#pragma once + +class CFileLoader +{ + static char ms_line[256]; +public: + static void LoadLevel(const char *filename); + static void LoadCollisionFromDatFile(int currlevel); + static char *LoadLine(int fd); + static RwTexDictionary *LoadTexDictionary(const char *filename); + static void LoadCollisionFile(const char *filename); + static void LoadCollisionModel(uint8 *buf, CColModel &model, char *name); + static void LoadModelFile(const char *filename); + static RpAtomic *FindRelatedModelInfoCB(RpAtomic *atomic, void *data); + static void LoadClumpFile(const char *filename); + static bool LoadClumpFile(RwStream *stream, uint32 id); + static bool StartLoadClumpFile(RwStream *stream, uint32 id); + static bool FinishLoadClumpFile(RwStream *stream, uint32 id); + static bool LoadAtomicFile(RwStream *stream, uint32 id); + static RpAtomic *SetRelatedModelInfoCB(RpAtomic *atomic, void *data); + static RpClump *LoadAtomicFile2Return(const char *filename); + static void AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src); + + static void LoadObjectTypes(const char *filename); + static void LoadObject(const char *line); + static void LoadTimeObject(const char *line); + static void LoadClumpObject(const char *line); + static void LoadVehicleObject(const char *line); + static void LoadPedObject(const char *line); + static int LoadPathHeader(const char *line, char *type); + static void LoadPedPathNode(const char *line, int id, int node); + static void LoadCarPathNode(const char *line, int id, int node); + static void Load2dEffect(const char *line); + + static void LoadScene(const char *filename); + static void LoadObjectInstance(const char *line); + static void LoadZone(const char *line); + static void LoadCullZone(const char *line); + static void LoadPickup(const char *line); + + static void LoadMapZones(const char *filename); +}; diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp new file mode 100644 index 00000000..954fcdef --- /dev/null +++ b/src/core/FileMgr.cpp @@ -0,0 +1,300 @@ +#define _CRT_SECURE_NO_WARNINGS +#include <fcntl.h> +#include <direct.h> +#include "common.h" +#include "patcher.h" +#include "FileMgr.h" + +const char *_psGetUserFilesFolder(); + +/* + * Windows FILE is BROKEN for GTA. + * + * We need to support mapping between LF and CRLF for text files + * but we do NOT want to end the file at the first sight of a SUB character. + * So here is a simple implementation of a FILE interface that works like GTA expects. + */ + +struct myFILE +{ + bool isText; + FILE *file; +}; + +#define NUMFILES 20 +static myFILE myfiles[NUMFILES]; + +/* Force file to open as binary but remember if it was text mode */ +static int +myfopen(const char *filename, const char *mode) +{ + int fd; + char realmode[10], *p; + + for(fd = 1; fd < NUMFILES; fd++) + if(myfiles[fd].file == nil) + goto found; + return 0; // no free fd +found: + myfiles[fd].isText = strchr(mode, 'b') == nil; + p = realmode; + while(*mode) + if(*mode != 't' && *mode != 'b') + *p++ = *mode++; + else + mode++; + *p++ = 'b'; + *p = '\0'; + myfiles[fd].file = fopen(filename, realmode); + if(myfiles[fd].file == nil) + return 0; + return fd; +} + +static int +myfclose(int fd) +{ + int ret; + assert(fd < NUMFILES); + if(myfiles[fd].file){ + ret = fclose(myfiles[fd].file); + myfiles[fd].file = nil; + return ret; + } + return EOF; +} + +static int +myfgetc(int fd) +{ + int c; + c = fgetc(myfiles[fd].file); + if(myfiles[fd].isText && c == 015){ + /* translate CRLF to LF */ + c = fgetc(myfiles[fd].file); + if(c == 012) + return c; + ungetc(c, myfiles[fd].file); + return 015; + } + return c; +} + +static int +myfputc(int c, int fd) +{ + /* translate LF to CRLF */ + if(myfiles[fd].isText && c == 012) + fputc(015, myfiles[fd].file); + return fputc(c, myfiles[fd].file); +} + +static char* +myfgets(char *buf, int len, int fd) +{ + int c; + char *p; + + p = buf; + len--; // NUL byte + while(len--){ + c = myfgetc(fd); + if(c == EOF){ + if(p == buf) + return nil; + break; + } + *p++ = c; + if(c == '\n') + break; + } + *p = '\0'; + return buf; +} + +static int +myfread(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + char *p; + size_t i; + int c; + + n *= elt; + p = (char*)buf; + for(i = 0; i < n; i++){ + c = myfgetc(fd); + if(c == EOF) + break; + *p++ = c; + } + return i / elt; + } + return fread(buf, elt, n, myfiles[fd].file); +} + +static int +myfwrite(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + char *p; + size_t i; + int c; + + n *= elt; + p = (char*)buf; + for(i = 0; i < n; i++){ + c = *p++; + myfputc(c, fd); + if(feof(myfiles[fd].file)) // is this right? + break; + } + return i / elt; + } + return fwrite(buf, elt, n, myfiles[fd].file); +} + +static int +myfseek(int fd, long offset, int whence) +{ + return fseek(myfiles[fd].file, offset, whence); +} + +static int +myfeof(int fd) +{ + return feof(myfiles[fd].file); +// return ferror(myfiles[fd].file); +} + + +char *CFileMgr::ms_rootDirName = (char*)0x5F18F8; +char *CFileMgr::ms_dirName = (char*)0x713CA8; + +void +CFileMgr::Initialise(void) +{ + _getcwd(ms_rootDirName, 128); + strcat(ms_rootDirName, "\\"); +} + +void +CFileMgr::ChangeDir(const char *dir) +{ + if(*dir == '\\'){ + strcpy(ms_dirName, ms_rootDirName); + dir++; + } + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + chdir(ms_dirName); +} + +void +CFileMgr::SetDir(const char *dir) +{ + strcpy(ms_dirName, ms_rootDirName); + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + chdir(ms_dirName); +} + +void +CFileMgr::SetDirMyDocuments(void) +{ + SetDir(""); // better start at the root if user directory is relative + chdir(_psGetUserFilesFolder()); +} + +int +CFileMgr::LoadFile(const char *file, uint8 *buf, int unused, const char *mode) +{ + int fd; + int n, len; + + fd = myfopen(file, mode); + if(fd == 0) + return 0; + len = 0; + do{ + n = myfread(buf + len, 1, 0x4000, fd); + if(n < 0) + return -1; + len += n; + }while(n == 0x4000); + buf[len] = 0; + myfclose(fd); + return len; +} + +int +CFileMgr::OpenFile(const char *file, const char *mode) +{ + return myfopen(file, mode); +} + +int +CFileMgr::OpenFileForWriting(const char *file) +{ + return OpenFile(file, "wb"); +} + +int +CFileMgr::Read(int fd, char *buf, int len) +{ + return myfread(buf, 1, len, fd); +} + +int +CFileMgr::Write(int fd, char *buf, int len) +{ + return myfwrite(buf, 1, len, fd); +} + +bool +CFileMgr::Seek(int fd, int offset, int whence) +{ + return !!myfseek(fd, offset, whence); +} + +bool +CFileMgr::ReadLine(int fd, char *buf, int len) +{ + return myfgets(buf, len, fd) != nil; +} + +int +CFileMgr::CloseFile(int fd) +{ + return myfclose(fd); +} + +int +CFileMgr::GetErrorReadWrite(int fd) +{ + return myfeof(fd); +} + +STARTPATCHES + InjectHook(0x478F80, CFileMgr::Initialise, PATCH_JUMP); + InjectHook(0x478FB0, CFileMgr::ChangeDir, PATCH_JUMP); + InjectHook(0x479020, CFileMgr::SetDir, PATCH_JUMP); + InjectHook(0x479080, CFileMgr::SetDirMyDocuments, PATCH_JUMP); + InjectHook(0x479090, CFileMgr::LoadFile, PATCH_JUMP); + InjectHook(0x479100, CFileMgr::OpenFile, PATCH_JUMP); + InjectHook(0x479120, CFileMgr::OpenFileForWriting, PATCH_JUMP); + InjectHook(0x479140, CFileMgr::Read, PATCH_JUMP); + InjectHook(0x479160, CFileMgr::Write, PATCH_JUMP); + InjectHook(0x479180, CFileMgr::Seek, PATCH_JUMP); + InjectHook(0x4791D0, CFileMgr::ReadLine, PATCH_JUMP); + InjectHook(0x479200, CFileMgr::CloseFile, PATCH_JUMP); + InjectHook(0x479210, CFileMgr::GetErrorReadWrite, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/FileMgr.h b/src/core/FileMgr.h new file mode 100644 index 00000000..bab86e38 --- /dev/null +++ b/src/core/FileMgr.h @@ -0,0 +1,21 @@ +#pragma once + +class CFileMgr +{ + static char *ms_rootDirName; //[128]; + static char *ms_dirName; //[128]; +public: + static void Initialise(void); + static void ChangeDir(const char *dir); + static void SetDir(const char *dir); + static void SetDirMyDocuments(void); + static int LoadFile(const char *file, uint8 *buf, int unused, const char *mode); + static int OpenFile(const char *file, const char *mode); + static int OpenFileForWriting(const char *file); + static int Read(int fd, char *buf, int len); + static int Write(int fd, char *buf, int len); + static bool Seek(int fd, int offset, int whence); + static bool ReadLine(int fd, char *buf, int len); + static int CloseFile(int fd); + static int GetErrorReadWrite(int fd); +}; diff --git a/src/core/Fire.cpp b/src/core/Fire.cpp new file mode 100644 index 00000000..05d72199 --- /dev/null +++ b/src/core/Fire.cpp @@ -0,0 +1,5 @@ +#include "common.h" +#include "patcher.h" +#include "Fire.h" + +WRAPPER void CFire::Extinguish(void) { EAXJMP(0x479D40); }
\ No newline at end of file diff --git a/src/core/Fire.h b/src/core/Fire.h new file mode 100644 index 00000000..c7f83fd8 --- /dev/null +++ b/src/core/Fire.h @@ -0,0 +1,23 @@ +#pragma once +#include "common.h" +#include "Entity.h" + +class CFire +{ + char m_bIsOngoing; + char m_bExists; + char m_bPropogationFlag; + char m_bAudioSet; + CVector m_vecPos; + CEntity *m_pEntity; + CEntity *m_pSource; + int m_nExtinguishTime; + int m_nStartTime; + int field_20; + int field_24; + int field_28; + float field_2C; + +public: + void Extinguish(void); +};
\ No newline at end of file diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp new file mode 100644 index 00000000..fdb2420b --- /dev/null +++ b/src/core/Frontend.cpp @@ -0,0 +1,2353 @@ +#define DIRECTINPUT_VERSION 0x0800 +#include <dinput.h> +#include "common.h" +#include "patcher.h" +#include "win.h" +#include "Frontend.h" +#include "Font.h" +#include "Pad.h" +#include "Text.h" +#include "main.h" +#include "Timer.h" +#include "Game.h" +#include "DMAudio.h" +#include "MusicManager.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "General.h" +#include "PCSave.h" +#include "Script.h" +#include "Camera.h" +#include "MenuScreens.h" +#include "ControllerConfig.h" +#include "Vehicle.h" +#include "MBlur.h" +#include "PlayerSkin.h" + +int32 &CMenuManager::OS_Language = *(int32*)0x5F2F78; +int8 &CMenuManager::m_PrefsUseVibration = *(int8*)0x95CD92; +int8 &CMenuManager::m_DisplayControllerOnFoot = *(int8*)0x95CD8D; +int8 &CMenuManager::m_PrefsVsync = *(int8*)0x5F2E58; +int8 &CMenuManager::m_PrefsVsyncDisp = *(int8*)0x5F2E5C; +int8 &CMenuManager::m_PrefsFrameLimiter = *(int8*)0x5F2E60; +int8 &CMenuManager::m_PrefsShowSubtitles = *(int8*)0x5F2E54; +int8 &CMenuManager::m_PrefsSpeakers = *(int8*)0x95CD7E; +int8 &CMenuManager::m_ControlMethod = *(int8*)0x8F5F7C; +int8 &CMenuManager::m_PrefsDMA = *(int8*)0x5F2F74; +int8 &CMenuManager::m_PrefsLanguage = *(int8*)0x941238; + +bool &CMenuManager::m_PrefsAllowNastyGame = *(bool*)0x5F2E64; +bool &CMenuManager::m_bStartUpFrontEndRequested = *(bool*)0x95CCF4; +bool &CMenuManager::m_bShutDownFrontEndRequested = *(bool*)0x95CD6A; + +int8 &CMenuManager::m_PrefsUseWideScreen = *(int8*)0x95CD23; +int8 &CMenuManager::m_PrefsRadioStation = *(int8*)0x95CDA4; +int8 &CMenuManager::m_bDisableMouseSteering = *(int8*)0x60252C; +int32 &CMenuManager::m_PrefsBrightness = *(int32*)0x5F2E50; +float &CMenuManager::m_PrefsLOD = *(float*)0x8F42C4; +int8 &CMenuManager::m_bFrontEnd_ReloadObrTxtGxt = *(int8*)0x628CFC; +int32 &CMenuManager::m_PrefsMusicVolume = *(int32*)0x5F2E4C; +int32 &CMenuManager::m_PrefsSfxVolume = *(int32*)0x5F2E48; + +uint8 *CMenuManager::m_PrefsSkinFile = (uint8*)0x5F2E74; + +CMenuManager &FrontEndMenuManager = *(CMenuManager*)0x8F59D8; + +// Move this somewhere else. +float lodMultiplier = *(float*)0x5F726C; + +// Stuff not in CMenuManager: +int VibrationTime; +char* pEditString; +int32 pControlEdit; +int8 DisplayComboButtonErrMsg; +bool MouseButtonJustClicked; +bool JoyButtonJustClicked; + +// Frontend inputs. +bool GetPadBack(); +bool GetPadExitEnter(); +bool GetPadForward(); +bool GetPadMoveUp(); +bool GetPadMoveDown(); +bool GetPadMoveLeft(); +bool GetPadMoveRight(); +bool GetMouseForward(); +bool GetMouseBack(); +bool GetMousePos(); +bool GetMouseMoveLeft(); +bool GetMouseMoveRight(); +bool GetPadInput(); +bool GetMouseInput(); + +char *FrontendFilenames[] = { + "fe2_mainpanel_ul", + "fe2_mainpanel_ur", + "fe2_mainpanel_dl", + "fe2_mainpanel_dr", + "fe2_mainpanel_dr2", + "fe2_tabactive", + "fe_iconbrief", + "fe_iconstats", + "fe_iconcontrols", + "fe_iconsave", + "fe_iconaudio", + "fe_icondisplay", + "fe_iconlanguage", + "fe_controller", + "fe_controllersh", + "fe_arrows1", + "fe_arrows2", + "fe_arrows3", + "fe_arrows4", + "fe_radio1", // HEAD_RADIO + "fe_radio2", // DOUBLE_CLEF + "fe_radio5", // JAH_RADIO + "fe_radio7", // RISE_FM + "fe_radio8", // LIPS_106 + "fe_radio3", // GAME_FM + "fe_radio4", // MSX_FM + "fe_radio6", // FLASHBACK + "fe_radio9", // CHATTERBOX +}; + +char *MenuFilenames[] = { + "connection24", "", + "findgame24", "", + "hostgame24", "", + "mainmenu24", "", + "Playersetup24", "", + "singleplayer24", "", + "multiplayer24", "", + "dmalogo128", "dmalogo128m", + "gtaLogo128", "gtaLogo128", + "rockstarLogo128", "rockstarlogo128m", + "gamespy256", "gamespy256a", + "mouse", "mousetimera", + "mousetimer", "mousetimera", + "mp3logo", "mp3logoA", + "downOFF", "buttonA", + "downON", "buttonA", + "upOFF", "buttonA", + "upON", "buttonA", + "gta3logo256", "gta3logo256m", + nil, nil +}; + +#if 1 +WRAPPER void CMenuManager::BuildStatLine(char *, void *, uint16, void *) { EAXJMP(0x483870); } +#else +void CMenuManager::BuildStatLine(char *, void *, uint16, void *) +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::CentreMousePointer() { EAXJMP(0x48ACE0); } +#else +void CMenuManager::CentreMousePointer() +{ + tagPOINT Point; + + if (SCREEN_WIDTH * 0.5f == 0.0f && 0.0f == SCREEN_HEIGHT * 0.5f) { + Point.x = SCREEN_WIDTH / 2; + Point.y = SCREEN_HEIGHT / 2; + ClientToScreen(PSGLOBAL(window), &Point); + SetCursorPos(Point.x, Point.y); + + PSGLOBAL(lastMousePos.x) = SCREEN_WIDTH / 2; + PSGLOBAL(lastMousePos.y) = SCREEN_HEIGHT / 2; + } +} +#endif + +#if 1 +WRAPPER void CMenuManager::CheckCodesForControls(int, int) { EAXJMP(0x48A950); } +#else +void CMenuManager::CheckCodesForControls() +{ + +} +#endif + +#if 0 +WRAPPER bool CMenuManager::CheckHover(int, int, int, int) { EAXJMP(0x48ACA0); } +#else +bool CMenuManager::CheckHover(int x1, int x2, int y1, int y2) +{ + return m_nMousePosX > x1 && m_nMousePosX < x2 && + m_nMousePosY > y1 && m_nMousePosY < y2; +} +#endif + +void CMenuManager::CheckSliderMovement(int value) +{ + float fBrightness = 0.0f; + float fDrawDistance = 0.0f; + float fRadioVolume = 0.0f; + float fSfxVolume = 0.0f; + float fMouseSens = 0.0f; + + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_BRIGHTNESS: + fBrightness = m_PrefsBrightness + (value * (512.0f) / 16.0f); + + if (fBrightness > 511.0f) + fBrightness = 511.0f; + else if (fBrightness < 0.0f) + fBrightness = 0.0f; + + m_PrefsBrightness = fBrightness; + SaveSettings(); + break; + case MENUACTION_DRAWDIST: + fDrawDistance = m_PrefsLOD + (value * (1.8f - 0.8f) / 16.0f); + + if (fDrawDistance > 1.8f) + fDrawDistance = 1.8f; + else if (fDrawDistance < 0.8f) + fDrawDistance = 0.8f; + + m_PrefsLOD = fDrawDistance; + SaveSettings(); + break; + case MENUACTION_MUSICVOLUME: + fRadioVolume = m_PrefsMusicVolume + (value * (128.0f) / 16.0f); + + if (fRadioVolume > 127.0f) + fRadioVolume = 127.0f; + else if (fRadioVolume < 0.0f) + fRadioVolume = 0.0f; + + m_PrefsMusicVolume = fRadioVolume; + DMAudio.SetMusicMasterVolume(fRadioVolume); + SaveSettings(); + break; + case MENUACTION_SFXVOLUME: + fSfxVolume = m_PrefsSfxVolume + (value * (128.0f) / 16.0f); + + if (fSfxVolume > 127) + fSfxVolume = 127; + else if (fSfxVolume < 0.0f) + fSfxVolume = 0.0f; + + m_PrefsSfxVolume = fSfxVolume; + DMAudio.SetEffectsMasterVolume(fSfxVolume); + SaveSettings(); + break; + case MENUACTION_MOUSESENS: + fMouseSens = TheCamera.m_fMouseAccelHorzntl + (value * (0.005f - 0.0003125f) / 16.0f); + + if (fMouseSens > 0.005f) + fMouseSens = 0.005f; + else if (fMouseSens < 0.0003125f) + fMouseSens = 0.0003125f; + + TheCamera.m_fMouseAccelHorzntl = fMouseSens; + + // BUG: game doesn't set Y Axis. + TheCamera.m_fMouseAccelVertical = fMouseSens; + SaveSettings(); + break; + }; +} + +#if 1 +WRAPPER int CMenuManager::CostructStatLine(int) { EAXJMP(0x482800); } +#else +int CMenuManager::CostructStatLine(int) +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::DisplayHelperText() { EAXJMP(0x48B490); } +#else +void CMenuManager::DisplayHelperText() +{ + static int32 AlphaText = 255; + static int32 Time = 0; + + if (m_nHelperTextMsgId && m_nHelperTextMsgId != 1) { + if (CTimer::GetTimeInMillisecondsPauseMode() - Time > 10) { + Time = CTimer::GetTimeInMillisecondsPauseMode(); + m_nHelperTextAlpha -= 2; + + if (AlphaText < 1) + ResetHelperText(); + + AlphaText = m_nHelperTextAlpha > 255 ? 255 : m_nHelperTextAlpha; + } + } + + wchar *HelperTextToPrint = nil; + // TODO: name this cases? + switch (m_nHelperTextMsgId) { + case 0: + HelperTextToPrint = TheText.Get("FET_MIG"); + break; + case 1: + HelperTextToPrint = TheText.Get("FET_APP"); + break; + case 2: + HelperTextToPrint = TheText.Get("FET_HRD"); + break; + case 3: + HelperTextToPrint = TheText.Get("FET_RSO"); + break; + case 4: + HelperTextToPrint = TheText.Get("FET_RSC"); + break; + default: + break; + }; + + CFont::SetAlignment(ALIGN_CENTER); + CFont::SetScale(SCREEN_SCALE_X(0.4f), SCREEN_SCALE_Y(0.6f)); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetDropColor(CRGBA(0, 0, 0, AlphaText)); + CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE); + CFont::SetColor(CRGBA(255, 255, 255, AlphaText)); + + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_SCALE_FROM_BOTTOM(120.0f), HelperTextToPrint); +} +#endif + +#if 0 +WRAPPER float CMenuManager::DisplaySlider(float, float, float, float, float, float) { EAXJMP(0x488420); } +#else +float CMenuManager::DisplaySlider(float x, float y, float leftSize, float rightSize, float rectSize, float progress) +{ + CRGBA color; + float sizeRange; + + float input = 0.0f; + for (int i = 0; i < 16; i++) { + input = i * rectSize/16.0f + x; + + if (i/16.0f + 1/32.0f < progress) + color = CRGBA(255, 217, 106, FadeIn(255)); + else + color = CRGBA(185, 120, 0, FadeIn(255)); + + sizeRange = max(leftSize, rightSize); + + float _x = i * rectSize/16.0f + x; + float _y = y + sizeRange - ((16 - i) * leftSize + i * rightSize)/16.0f; + float _w = SCREEN_SCALE_X(10.0f) + i * rectSize/16.0f + x; + float _h = y + sizeRange; + float _s = SCREEN_SCALE_X(2.0f); + CSprite2d::DrawRect(CRect(_x + _s, _y + _s, _w + _s, _h + _s), CRGBA(0, 0, 0, FadeIn(255))); // Shadow + CSprite2d::DrawRect(CRect(i * rectSize/16.0f + x, y + sizeRange - ((16 - i) * leftSize + i * rightSize)/16.0f, SCREEN_SCALE_X(10.0f) + i * rectSize/16.0f + x, y + sizeRange), color); + }; + return input; +} +#endif + +#if 0 +WRAPPER void CMenuManager::DoSettingsBeforeStartingAGame() { EAXJMP(0x48AB40); } +#else +void CMenuManager::DoSettingsBeforeStartingAGame() +{ + CCamera::m_bUseMouse3rdPerson = m_ControlMethod == 0; + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + + m_bStartGameLoading = true; + + ShutdownJustMenu(); + UnloadTextures(); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} +#endif + +#if 0 +WRAPPER void CMenuManager::Draw() { EAXJMP(0x47AE00); } +#else +void CMenuManager::Draw() +{ + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(40.0f)); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A))); + + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + PrintStats(); + break; + case MENUPAGE_BRIEFS: + PrintBriefs(); + break; + case MENUPAGE_CONTROLLER_DEBUG: + DrawControllerScreenExtraText(0, 350, 20); + break; + } + + // Header. + if (aScreens[m_nCurrScreen].m_ScreenName[0]) { + CFont::SetDropShadowPosition(0); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetScale(SCREEN_SCALE_X(MENUHEADER_WIDTH), SCREEN_SCALE_Y(MENUHEADER_HEIGHT)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + } + + // Action text. + wchar *str; + if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) { + switch (m_nCurrScreen) { + case MENUPAGE_LOAD_SLOT_CONFIRM: + if (m_bGameNotLoaded) + str = TheText.Get("FES_LCG"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_SAVE_OVERWRITE_CONFIRM: + if (Slots[m_nCurrSaveSlot] == 1) + str = TheText.Get("FESZ_QZ"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_EXIT: + if (m_bGameNotLoaded) + str = TheText.Get("FEQ_SRW"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + default: + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + }; + + CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A))); + CFont::SetFontStyle(FONT_BANK); + CFont::SetScale(SCREEN_SCALE_X(MENUACTION_WIDTH), SCREEN_SCALE_Y(MENUACTION_HEIGHT)); + CFont::SetAlignment(ALIGN_LEFT); + CFont::SetColor(CRGBA(235, 170, 50, FadeIn(255))); + CFont::PrintString(SCREEN_SCALE_X(MENUACTION_POS_X), SCREEN_SCALE_Y(MENUACTION_POS_Y), str); + } + + for (int i = 0; i < MENUROWS; ++i) { + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0]) { + wchar *textToPrint[MENUCOLUMNS] = { nil, nil }; + bool Locked = false; + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) { + textToPrint[MENUCOLUMN_LEFT] = GetNameOfSavedGame(i - 1); + textToPrint[MENUCOLUMN_RIGHT] = GetSavedGameDateAndTime(i - 1); + + if (!textToPrint[MENUCOLUMN_LEFT][0]) { + sprintf(gString, "FEM_SL%d", i); + textToPrint[MENUCOLUMN_LEFT] = TheText.Get(gString); + } + } + else { + textToPrint[MENUCOLUMN_LEFT] = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName); + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_SCREENRES) { + if (m_bGameNotLoaded) + Locked = false; + else + Locked = true; + } + } + + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_CTRLVIBRATION: + break; + case MENUACTION_CTRLCONFIG: + switch (CPad::GetPad(0)->Mode) { + case 0: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF1"); + break; + case 1: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF2"); + break; + case 2: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF3"); + break; + case 3: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEC_CF4"); + break; + }; + break; + case MENUACTION_CTRLDISPLAY: + break; + case MENUACTION_FRAMESYNC: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsVsyncDisp ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_FRAMELIMIT: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsFrameLimiter ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_TRAILS: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CMBlur::BlurOn ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SUBTITLES: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsShowSubtitles ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_WIDESCREEN: +#ifndef ASPECT_RATIO_SCALE + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsUseWideScreen ? "FEM_ON" : "FEM_OFF"); +#else + switch (m_PrefsUseWideScreen) { + case AR_AUTO: + textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"AUTO"; + break; + case AR_4_3: + textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"4:3"; + break; + case AR_16_9: + textToPrint[MENUCOLUMN_RIGHT] = (wchar*)L"16:9"; + break; + }; +#endif + break; + case MENUACTION_RADIO: + sprintf(gString, "FEA_FM%d", m_PrefsRadioStation); + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(gString); + break; + case MENUACTION_SETDBGFLAG: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CTheScripts::DbgFlag ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(CTheScripts::DbgFlag ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_INVVERT: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(MousePointerStateHelper.bInvertVertically ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SCREENRES: + { + char *res = _psGetVideoModeList()[m_nDisplayVideoMode]; + + if (!res) + res = ""; + + AsciiToUnicode(res, gUString); + textToPrint[MENUCOLUMN_RIGHT] = gUString; + } + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex == -1) + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_NAH"); + else { + char *provider = MusicManager.Get3DProviderName(m_nPrefsAudio3DProviderIndex); + AsciiToUnicode(provider, gUString); + textToPrint[MENUCOLUMN_RIGHT] = gUString; + } + break; + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex == -1) + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_NAH"); + else { + switch (m_PrefsSpeakers) { + case 0: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_2SP"); + break; + case 1: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_EAR"); + break; + case 2: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get("FEA_4SP"); + break; + }; + } + break; + case MENUACTION_CTRLMETHOD: + switch (m_ControlMethod) { + case 0: + textToPrint[MENUCOLUMN_LEFT] = TheText.Get("FET_SCN"); + break; + case 1: + textToPrint[MENUCOLUMN_LEFT] = TheText.Get("FET_CCN"); + break; + }; + break; + case MENUACTION_DYNAMICACOUSTIC: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_PrefsDMA ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_MOUSESTEER: + textToPrint[MENUCOLUMN_RIGHT] = TheText.Get(m_bDisableMouseSteering ? "FEM_ON" : "FEM_OFF"); + break; + }; + + CFont::SetDropShadowPosition(MENUDROP_COLOR_SIZE); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(MENUDROP_COLOR_A))); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetRightJustifyWrap(-SCREEN_WIDTH); + + // Set alignment. + CVector2D vecPositions = { 0.0f, 0.0f }; + float fVerticalSpacing; + float fBarSize; + + int SavePageSlot = + m_nCurrScreen == MENUPAGE_CHOOSE_LOAD_SLOT || + m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT || + m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT; + + if (SavePageSlot) { + CFont::SetFontStyle(FONT_BANK); + CFont::SetAlignment(ALIGN_LEFT); + CFont::SetScale(SCREEN_SCALE_X(0.45f), SCREEN_SCALE_Y(0.7f)); + fVerticalSpacing = MENUCOLUMN_SPACING_MIN; + fBarSize = MENUSELECT_BOX_MIN; + + vecPositions.x = SCREEN_SCALE_X(MENUCOLUMN_SAVE_X); + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_SAVE_Y); + } + else { + CFont::SetFontStyle(FONT_HEADING); + + int LeftMenuColumn = + m_nCurrScreen == MENUPAGE_SOUND_SETTINGS || + m_nCurrScreen == MENUPAGE_GRAPHICS_SETTINGS || + m_nCurrScreen == MENUPAGE_MOUSE_CONTROLS; + + if (LeftMenuColumn) { + CFont::SetAlignment(ALIGN_LEFT); + CFont::SetScale(SCREEN_SCALE_X(0.55f), SCREEN_SCALE_Y(0.8f)); + fVerticalSpacing = MENUCOLUMN_SPACING_MIN; + fBarSize = MENUSELECT_BOX_MIN; + } + else { + CFont::SetAlignment(ALIGN_CENTER); + CFont::SetScale(SCREEN_SCALE_X(0.75f), SCREEN_SCALE_Y(0.9f)); + fVerticalSpacing = MENUCOLUMN_SPACING_MAX; + fBarSize = MENUSELECT_BOX_MAX; + } + + // Set positions. + if (CFont::GetDetails().centre) + vecPositions.x = SCREEN_WIDTH / 2; + else + vecPositions.x = SCREEN_SCALE_X(MENUCOLUMN_POS_X); + + switch (m_nCurrScreen) { + case MENUPAGE_BRIEFS: + case MENUPAGE_STATS: + vecPositions.y = SCREEN_SCALE_FROM_BOTTOM(MENUCOLUMN_FEDS); + break; + case MENUPAGE_SOUND_SETTINGS: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MAX_Y); + + if (i > 5) + vecPositions.y += SCREEN_SCALE_Y(MENURADIO_ICON_H * 1.16f); + break; + case MENUPAGE_LANGUAGE_SETTINGS: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MIN_Y); + break; + case MENUPAGE_MOUSE_CONTROLS: + case MENUPAGE_GRAPHICS_SETTINGS: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MAX_Y); + break; + case MENUPAGE_OPTIONS: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y); + break; + case MENUPAGE_PAUSE_MENU: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_PAUSE_Y); + break; + case MENUPAGE_NEW_GAME: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y); + break; + case MENUPAGE_START_MENU: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_START_Y); + break; + default: + vecPositions.y = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENUCOLUMN_MID_Y); + break; + }; + } + + if (i > 0) + vecPositions.y += SCREEN_SCALE_Y(fVerticalSpacing * i); + + // Set color and draw selection bar. + if (i == m_nCurrOption && m_nMenuFadeAlpha >= 255) { + CFont::SetColor(CRGBA(255, 217, 106, FadeIn(255))); + CSprite2d::DrawRect(CRect(SCREEN_STRETCH_X(11.0f), vecPositions.y - SCREEN_STRETCH_Y(fBarSize * 0.13f), SCREEN_STRETCH_FROM_RIGHT(11.0f), vecPositions.y + SCREEN_STRETCH_Y(fBarSize)), CRGBA(100, 200, 50, 50)); + } + else + CFont::SetColor(CRGBA(235, 170, 50, FadeIn(255))); + + // Draw + if (textToPrint[MENUCOLUMN_LEFT]) + CFont::PrintString(vecPositions.x, vecPositions.y, textToPrint[MENUCOLUMN_LEFT]); + + if (textToPrint[MENUCOLUMN_RIGHT]) { + if (Locked) + CFont::SetColor(CRGBA(190, 130, 40, FadeIn(255))); + + CFont::SetAlignment(ALIGN_RIGHT); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SavePageSlot ? MENUCOLUMN_SAVE_X : MENUCOLUMN_POS_X), vecPositions.y, textToPrint[MENUCOLUMN_RIGHT]); + } + + // Mouse support. + // TODO: inputs for these pages. + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + } + else if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + + } + else { + static bool bIsMouseInPosition = false; + if (m_nMenuFadeAlpha >= 255 && GetMouseInput()) { + CVector2D vecInputSize = { SCREEN_SCALE_X(20.0f), SCREEN_SCALE_FROM_RIGHT(20.0f) }; + if (m_bShowMouse && + ((CheckHover(vecInputSize.x, vecInputSize.y, vecPositions.y, vecPositions.y + SCREEN_STRETCH_Y(20.0f))))) + bIsMouseInPosition = true; + else + bIsMouseInPosition = false; + + if (bIsMouseInPosition) { + if (m_nCurrOption != i) { + m_nCurrOption = i; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0); + } + + m_nPrevOption = m_nCurrOption; + + if (GetMouseForward()) + m_nHoverOption = IGNORE_OPTION; + else + m_nHoverOption = ACTIVATE_OPTION; + } + } + } + + // Sliders + // TODO: CheckHover + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_BRIGHTNESS: + DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsBrightness/512.0f); + break; + case MENUACTION_DRAWDIST: + DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), (m_PrefsLOD - 0.8f) * 1.0f); + break; + case MENUACTION_MUSICVOLUME: + DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsMusicVolume/128.0f); + break; + case MENUACTION_SFXVOLUME: + DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), m_PrefsSfxVolume/128.0f); + break; + case MENUACTION_MOUSESENS: + DisplaySlider(SCREEN_SCALE_FROM_RIGHT(MENUSLIDER_X), vecPositions.y - SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(18.0f), SCREEN_SCALE_X(256.0f), TheCamera.m_fMouseAccelHorzntl * 200.0f); + break; + }; + + // Radio icons. + float fIconSpacing = 59.52f; + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + for (int i = 0; i < POLICE_RADIO; i++) { +#ifndef ASPECT_RATIO_SCALE + if (i < USERTRACK) + m_aFrontEndSprites[i + FE_RADIO1].Draw(SCREEN_STRETCH_X(MENURADIO_ICON_X + fIconSpacing * i), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170)); + if (i > CHATTERBOX && DMAudio.IsMP3RadioChannelAvailable()) + m_aMenuSprites[MENUSPRITE_MP3LOGO].Draw(SCREEN_STRETCH_X(MENURADIO_ICON_X + fIconSpacing * i), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170)); +#else + float fMp3Pos = 0.0f; + if (DMAudio.IsMP3RadioChannelAvailable()) + fMp3Pos = 34.0f; + + if (i < USERTRACK) + m_aFrontEndSprites[i + FE_RADIO1].Draw((SCREEN_WIDTH * 0.5) + SCREEN_SCALE_X(-fMp3Pos + MENURADIO_ICON_X + (fIconSpacing * i)), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170)); + if (i > CHATTERBOX && DMAudio.IsMP3RadioChannelAvailable()) + m_aMenuSprites[MENUSPRITE_MP3LOGO].Draw((SCREEN_WIDTH * 0.5) + SCREEN_SCALE_X(-fMp3Pos + MENURADIO_ICON_X + (fIconSpacing * i)), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(MENURADIO_ICON_Y), SCREEN_SCALE_X(MENURADIO_ICON_W), SCREEN_SCALE_Y(MENURADIO_ICON_H), i == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : CRGBA(225, 0, 0, 170)); +#endif + } + } + + // Helpers + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES")) { + if (m_nDisplayVideoMode == m_nPrefsVideoMode) { + if (m_nHelperTextMsgId == 1) + ResetHelperText(); + } + else + SetHelperText(1); + } + else { + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + m_nDisplayVideoMode = m_nPrefsVideoMode; + SetHelperText(3); + } + } + + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_SETTINGS: + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_GRAPHICS_SETTINGS: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_MOUSE_CONTROLS: + DisplayHelperText(); + break; + }; + } + }; +} +#endif + +#if 1 +WRAPPER void CMenuManager::DrawControllerBound(int, int, int, uint8) { EAXJMP(0x489710); } +#else +void CMenuManager::DrawControllerBound(int, int, int, uint8) +{ + +} +#endif + +#if 1 +WRAPPER void CMenuManager::DrawControllerScreenExtraText(int, int, int) { EAXJMP(0x4892F0); } +#else +void CMenuManager::DrawControllerScreenExtraText(int, int, int) +{ + +} +#endif + +#if 1 +WRAPPER void CMenuManager::DrawControllerSetupScreen() { EAXJMP(0x481210); } +#else +void CMenuManager::DrawControllerSetupScreen() +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::DrawFrontEnd(void) { EAXJMP(0x47A540); } +#else +void CMenuManager::DrawFrontEnd() +{ + CFont::SetAlphaFade(255.0f); + + if (m_nCurrScreen == MENUPAGE_NONE) { + m_nMenuFadeAlpha = 0; + + if (m_bGameNotLoaded) + m_nCurrScreen = MENUPAGE_START_MENU; + else + m_nCurrScreen = MENUPAGE_PAUSE_MENU; + } + + if (!m_nCurrOption && aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) + m_nCurrOption = MENUROW_1; + + CMenuManager::DrawFrontEndNormal(); + CMenuManager::PrintErrorMessage(); +} +#endif + +#if 0 +WRAPPER void CMenuManager::DrawFrontEndNormal(void) { EAXJMP(0x47A5B0); } +#else +void CMenuManager::DrawFrontEndNormal() +{ + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSCLAMP); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + eMenuSprites previousSprite = MENUSPRITE_MAINMENU; + if (m_nMenuFadeAlpha < 255) { + switch (m_nPrevScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + previousSprite = MENUSPRITE_MAINMENU; + break; + case MENUPAGE_NEW_GAME: + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_EXIT: + previousSprite = MENUSPRITE_SINGLEPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAIN: + previousSprite = MENUSPRITE_MULTIPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAP: + case MENUPAGE_MULTIPLAYER_FIND_GAME: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + previousSprite = MENUSPRITE_FINDGAME; + break; + case MENUPAGE_MULTIPLAYER_CONNECTION: + case MENUPAGE_MULTIPLAYER_MODE: + previousSprite = MENUSPRITE_CONNECTION; + break; + case MENUPAGE_MULTIPLAYER_CREATE: + previousSprite = MENUSPRITE_HOSTGAME; + break; + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_OPTIONS: + previousSprite = MENUSPRITE_PLAYERSET; + break; + }; + + if (m_nPrevScreen == MENUPAGE_NONE) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255)); + else + m_aMenuSprites[previousSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + } + + eMenuSprites currentSprite = MENUSPRITE_MAINMENU; + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + currentSprite = MENUSPRITE_MAINMENU; + break; + case MENUPAGE_NEW_GAME: + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_EXIT: + currentSprite = MENUSPRITE_SINGLEPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAIN: + currentSprite = MENUSPRITE_MULTIPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAP: + case MENUPAGE_MULTIPLAYER_FIND_GAME: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + currentSprite = MENUSPRITE_FINDGAME; + break; + case MENUPAGE_MULTIPLAYER_CONNECTION: + case MENUPAGE_MULTIPLAYER_MODE: + currentSprite = MENUSPRITE_CONNECTION; + break; + case MENUPAGE_MULTIPLAYER_CREATE: + currentSprite = MENUSPRITE_HOSTGAME; + break; + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_OPTIONS: + currentSprite = MENUSPRITE_PLAYERSET; + break; + }; + + uint32 savedShade; + uint32 savedAlpha; + RwRenderStateGet(rwRENDERSTATESHADEMODE, &savedShade); + RwRenderStateSet(rwRENDERSTATESHADEMODE, reinterpret_cast<void *>(rwSHADEMODEGOURAUD)); + RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, &savedAlpha); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast<void *>(TRUE)); + if (m_nMenuFadeAlpha >= 255) { + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + } + else { + if (m_nMenuFadeAlpha < 255) { + m_nMenuFadeAlpha += 0.1f * 255.0f; + + if (m_nMenuFadeAlpha >= 255) + m_nMenuFadeAlpha = 255; + + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, m_nMenuFadeAlpha)); + } + else + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + } + + // GTA LOGO + if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_PAUSE_MENU) { + if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + m_aMenuSprites[MENUSPRITE_GTA3LOGO].Draw(CRect((SCREEN_WIDTH / 2) - SCREEN_SCALE_X(115.0f), SCREEN_SCALE_Y(70.0f), (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(115.0f), SCREEN_SCALE_Y(180.0f)), CRGBA(255, 255, 255, FadeIn(255))); + else + m_aMenuSprites[MENUSPRITE_GTALOGO].Draw(CRect((SCREEN_WIDTH / 2) - SCREEN_SCALE_X(95.0f), SCREEN_SCALE_Y(40.0f), (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(95.0f), SCREEN_SCALE_Y(210.0f)), CRGBA(255, 255, 255, FadeIn(255))); + } + RwRenderStateSet(rwRENDERSTATESHADEMODE, reinterpret_cast<void *>(savedShade)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast<void *>(savedAlpha)); + + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + CMenuManager::DrawPlayerSetupScreen(); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + CMenuManager::DrawControllerSetupScreen(); + break; + default: + CMenuManager::Draw(); + break; + }; + + CFont::DrawFonts(); + + // Draw mouse + if (m_bShowMouse) + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(m_nMousePosX, m_nMousePosY, SCREEN_SCALE_X(60.0f), SCREEN_SCALE_Y(60.0f), CRGBA(255, 255, 255, 255)); +} +#endif + +#if 1 +WRAPPER void CMenuManager::DrawPlayerSetupScreen() { EAXJMP(0x47F2B0); } +#else +void CMenuManager::DrawPlayerSetupScreen() +{ + +} +#endif + +#if 0 +WRAPPER int CMenuManager::FadeIn(int alpha) { EAXJMP(0x48AC60); } +#else +int CMenuManager::FadeIn(int alpha) +{ + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_DELETING) + return alpha; + + if (m_nMenuFadeAlpha >= alpha) + return alpha; + + return m_nMenuFadeAlpha; +} +#endif + +#if 1 +WRAPPER void CMenuManager::FilterOutColorMarkersFromString(uint16, CRGBA &) { EAXJMP(0x4889C0); } +#else +void CMenuManager::FilterOutColorMarkersFromString(uint16, CRGBA &) +{ + +} +#endif + +#if 1 +WRAPPER int CMenuManager::GetStartOptionsCntrlConfigScreens() { EAXJMP(0x489270); } +#else +int CMenuManager::GetStartOptionsCntrlConfigScreens() +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::InitialiseChangedLanguageSettings() { EAXJMP(0x47A4D0); } +#else +void CMenuManager::InitialiseChangedLanguageSettings() +{ + if (m_bFrontEnd_ReloadObrTxtGxt) { + m_bFrontEnd_ReloadObrTxtGxt = false; + CTimer::Stop(); + TheText.Unload(); + TheText.Load(); + CTimer::Update(); + CGame::frenchGame = false; + CGame::germanGame = false; + switch (CMenuManager::m_PrefsLanguage) { + case LANGUAGE_FRENCH: + CGame::frenchGame = true; + break; + case LANGUAGE_GERMAN: + CGame::germanGame = true; + break; + default: + break; + }; + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::LoadAllTextures() { EAXJMP(0x47A230); } +#else +void CMenuManager::LoadAllTextures() +{ + if (!m_bSpritesLoaded) { + CMenuManager::CentreMousePointer(); + DMAudio.ChangeMusicMode(0); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + m_nCurrOption = MENUROW_0; + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (CMenuManager::m_PrefsRadioStation > USERTRACK) + CMenuManager::m_PrefsRadioStation = CGeneral::GetRandomNumber() % 10; + } + else if (CMenuManager::m_PrefsRadioStation > CHATTERBOX) + CMenuManager::m_PrefsRadioStation = CGeneral::GetRandomNumber() % 9; + + CFileMgr::SetDir(""); + CTimer::Stop(); + CStreaming::MakeSpaceFor(700 * 1024); + CStreaming::ImGonnaUseStreamingMemory(); + CTxdStore::PushCurrentTxd(); + + int frontend = CTxdStore::AddTxdSlot("frontend"); + CTxdStore::LoadTxd(frontend, "MODELS/FRONTEND.TXD"); + CTxdStore::AddRef(frontend); + CTxdStore::SetCurrentTxd(frontend); + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); + + debug("LOAD frontend\n"); + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + }; + + CTxdStore::PopCurrentTxd(); + + int menu = CTxdStore::AddTxdSlot("menu"); + CTxdStore::LoadTxd(menu, "MODELS/MENU.TXD"); + CTxdStore::AddRef(menu); + CTxdStore::SetCurrentTxd(menu); + + debug("LOAD sprite\n"); + for (int i = 0; i < ARRAY_SIZE(MenuFilenames)/2; i++) { + m_aMenuSprites[i].SetTexture(MenuFilenames[i*2], MenuFilenames[i*2+1]); + m_aMenuSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + }; + + CTxdStore::PopCurrentTxd(); + + m_bSpritesLoaded = true; + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::LoadSettings() { EAXJMP(0x488EE0); } +#else +void CMenuManager::LoadSettings() +{ + + CFileMgr::SetDirMyDocuments(); + + uint8 prevLang = m_PrefsLanguage; + MousePointerStateHelper.bInvertVertically = true; + + static char Ver; + int fileHandle = CFileMgr::OpenFile("gta3.set", "r"); + if (fileHandle) { + CFileMgr::Read(fileHandle, buf(&Ver), sizeof(Ver)); + + if (strncmp(&Ver, "THIS FILE IS NOT VALID YET", 26)) { + CFileMgr::Seek(fileHandle, 0, 0); + ControlsManager.LoadSettings(fileHandle); + CFileMgr::Read(fileHandle, buf(&gString), 20); + CFileMgr::Read(fileHandle, buf(&gString), 20); + CFileMgr::Read(fileHandle, buf(&gString), 4); + CFileMgr::Read(fileHandle, buf(&gString), 4); + CFileMgr::Read(fileHandle, buf(&gString), 1); + CFileMgr::Read(fileHandle, buf(&gString), 1); + CFileMgr::Read(fileHandle, buf(&gString), 1); + CFileMgr::Read(fileHandle, buf(&TheCamera.m_bHeadBob), 1); + CFileMgr::Read(fileHandle, buf(&TheCamera.m_fMouseAccelHorzntl), 4); + CFileMgr::Read(fileHandle, buf(&TheCamera.m_fMouseAccelVertical), 4); + CFileMgr::Read(fileHandle, buf(&MousePointerStateHelper.bInvertVertically), 1); + CFileMgr::Read(fileHandle, buf(&CVehicle::m_bDisableMouseSteering), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsSfxVolume), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsMusicVolume), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsRadioStation), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsSpeakers), 1); + CFileMgr::Read(fileHandle, buf(&m_nPrefsAudio3DProviderIndex), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsDMA), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsBrightness), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsLOD), 4); + CFileMgr::Read(fileHandle, buf(&m_PrefsShowSubtitles), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsUseWideScreen), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsVsyncDisp), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsFrameLimiter), 1); + CFileMgr::Read(fileHandle, buf(&m_nDisplayVideoMode), 1); + CFileMgr::Read(fileHandle, buf(&CMBlur::BlurOn), 1); + CFileMgr::Read(fileHandle, buf(m_PrefsSkinFile), 256); + CFileMgr::Read(fileHandle, buf(&m_ControlMethod), 1); + CFileMgr::Read(fileHandle, buf(&m_PrefsLanguage), 1); + } + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + + m_PrefsVsync = m_PrefsVsyncDisp; + lodMultiplier = m_PrefsLOD; + + if (m_nPrefsAudio3DProviderIndex == -1) + m_nPrefsAudio3DProviderIndex = -2; + + if (m_PrefsLanguage == prevLang) + m_bLanguageLoaded = false; + else { + m_bLanguageLoaded = true; + TheText.Unload(); + TheText.Load(); + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + + debug("The previously saved language is now in use"); + } + + /*struct _WIN32_FIND_DATAA FindFileData; + HANDLE H = FindFirstFileA("skins\*.bmp", &FindFileData); + char Dest; + bool SkinFound = false; + + for (int i = 1; H != (HANDLE)-1 && i; i = FindNextFileA(H, &FindFileData)) { + strcpy(&Dest, buf(m_PrefsSkinFile)); + strcat(&Dest, ".bmp"); + if (!strcmp(FindFileData.cFileName, &Dest)) + SkinFound = true; + } + + FindClose(H); + + if (!SkinFound) { + debug("Default skin set as no other skins are available OR saved skin not found!"); + strcpy((char *)CMenuManager::m_PrefsSkinFile, "$$\"\""); + strcpy(m_aSkinName, "$$\"\""); + }*/ +} +#endif + +#if 1 +WRAPPER void CMenuManager::MessageScreen(char *) { EAXJMP(0x48B7E0); } +#else +void CMenuManager::MessageScreen(char *) +{ + +} +#endif + +#if 1 +WRAPPER void CMenuManager::PickNewPlayerColour() { EAXJMP(0x488C40); } +#else +void CMenuManager::PickNewPlayerColour() +{ + +} +#endif + +#if 1 +WRAPPER void CMenuManager::PrintBriefs() { EAXJMP(0x484D60); } +#else +void CMenuManager::PrintBriefs() +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::PrintErrorMessage() { EAXJMP(0x484F70); } +#else +void CMenuManager::PrintErrorMessage() +{ + if (!CPad::bDisplayNoControllerMessage && !CPad::bObsoleteControllerMessage) + return; + + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(140.0f), SCREEN_WIDTH - SCREEN_SCALE_X(20.0f), SCREEN_HEIGHT - SCREEN_SCALE_Y(140.0f)), CRGBA(64, 16, 16, 224)); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_WIDTH - 40.0f); + CFont::SetColor(CRGBA(165, 165, 165, 255)); + CFont::SetScale(SCREEN_SCALE_X(0.9f), SCREEN_SCALE_Y(0.9f)); + CFont::PrintString(SCREEN_SCALE_X(40.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(60.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT")); + CFont::DrawFonts(); +} +#endif + +#if 1 +WRAPPER void CMenuManager::PrintStats() { EAXJMP(0x482100); } +#else +void CMenuManager::PrintStats() +{ + +} +#endif + +#if 0 +WRAPPER void CMenuManager::Process(void) { EAXJMP(0x485100); } +#else +void CMenuManager::Process(void) +{ + if (m_bSaveMenuActive && TheCamera.GetScreenFadeStatus()) + return; + + field_113 = 0; + InitialiseChangedLanguageSettings(); + + SwitchMenuOnAndOff(); + + if (m_bMenuActive) { + LoadAllTextures(); + + if (m_nCurrScreen == MENUPAGE_DELETING) { + bool SlotPopulated = false; + + if (PcSaveHelper.DeleteSlot(m_nCurrSaveSlot)) { + PcSaveHelper.PopulateSlotInfo(); + SlotPopulated = true; + } + + if (SlotPopulated) { + m_nPrevScreen = m_nCurrScreen; + m_nCurrScreen = MENUPAGE_DELETE_SUCCESS; + m_nCurrOption = 0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS) { + int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot); + PcSaveHelper.PopulateSlotInfo(); + if (SaveSlot) { + m_nPrevScreen = m_nCurrScreen; + m_nCurrScreen = MENUPAGE_SAVE_SUCCESSFUL; + m_nCurrOption = 0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS) { + if (CheckSlotDataValid(m_nCurrSaveSlot)) { + TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == 0; + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + DMAudio.Service(); + m_bStartGameLoading = 1; + RequestFrontEndShutdown(); + m_bLoadingSavedGame = 1; + b_FoundRecentSavedGameWantToLoad = 1; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + } + else + SaveLoadFileError_SetUpErrorScreen(); + } + + ProcessButtonPresses(); + } + else { + if (GetPadExitEnter()) + RequestFrontEndStartUp(); + + UnloadTextures(); + m_nPrevScreen = MENUPAGE_NONE; + m_nCurrScreen = m_nPrevScreen; + m_nCurrOption = MENUROW_0; + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::ProcessButtonPresses() { EAXJMP(0x4856F0); } +#else +void CMenuManager::ProcessButtonPresses() +{ + // Update Mouse Position + m_nMouseOldPosX = m_nMousePosX; + m_nMouseOldPosY = m_nMousePosY; + + m_nMousePosX = m_nMouseTempPosX; + m_nMousePosY = m_nMouseTempPosY; + + if (m_nMousePosX < 0) + m_nMousePosX = 0; + if (m_nMousePosX > SCREEN_WIDTH) + m_nMousePosX = SCREEN_WIDTH; + if (m_nMousePosY < 0) + m_nMousePosY = 0; + if (m_nMousePosY > SCREEN_HEIGHT) + m_nMousePosY = SCREEN_HEIGHT; + + // Show/hide mouse cursor. + if (GetMouseInput()) + m_bShowMouse = true; + else if (GetPadInput()) + m_bShowMouse = false; + + // Get number of menu options. + uint8 NumberOfMenuOptions = GetNumberOfMenuOptions(); + + // Select next/previous option with pad. Mouse is done in drawing function. + if (GetPadMoveUp()) { + m_nPrevOption = m_nCurrOption; + m_nCurrOption -= 1; + + if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) { + if (m_nCurrOption < MENUROW_1) + m_nCurrOption = NumberOfMenuOptions; + } + else { + if (m_nCurrOption < MENUROW_0) + m_nCurrOption = NumberOfMenuOptions; + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0); + } + else if (GetPadMoveDown()) { + m_nPrevOption = m_nCurrOption; + m_nCurrOption += 1; + + if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) { + if (m_nCurrOption > NumberOfMenuOptions) + m_nCurrOption = MENUROW_1; + } + else { + if (m_nCurrOption > NumberOfMenuOptions) + m_nCurrOption = MENUROW_0; + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_DENIED, 0); + } + + // Set what happens if ESC is pressed. + if (GetPadBack()) { + bool PlayEscSound = false; + switch (m_nCurrScreen) { + case MENUPAGE_START_MENU: + break; + case MENUPAGE_CHOOSE_SAVE_SLOT: + case MENUPAGE_PAUSE_MENU: + RequestFrontEndShutdown(); + PlayEscSound = true; + break; + default: + SwitchToNewScreen(aScreens[m_nCurrScreen].m_PreviousPage[0]); + PlayEscSound = true; + break; + }; + + if (PlayEscSound) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0); + } + + // TODO: finish hover options. + // Set mouse buttons. + if (GetMouseForward()) { + switch (m_nHoverOption) { + case ACTIVATE_OPTION: + if (m_nCurrOption || m_nCurrScreen != MENUPAGE_PAUSE_MENU) + m_nCurrOption = m_nPrevOption; + + m_nHoverOption = ACTIVATE_OPTION; + break; + default: + break; + }; + } + + // Process all menu options here, but first check if it's an option or a redirect. + int32 CurrAction = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if ((GetPadForward() || GetMouseForward()) || + ((GetPadMoveLeft() || GetMouseMoveRight()) || (GetPadMoveRight() || GetMouseMoveLeft())) && + (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu == m_nCurrScreen && + CurrAction != MENUACTION_CHANGEMENU && + CurrAction != MENUACTION_LOADRADIO && + CurrAction != MENUACTION_RESTOREDEF && + CurrAction != MENUACTION_PLAYERSETUP)) { + + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB")) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0); + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SUCCESS, 0); + + ProcessOnOffMenuOptions(); + } + + // Process screens that may redirect you somewhere, or may not. + switch (m_nCurrScreen) { + case MENUPAGE_LOAD_SLOT_CONFIRM: + break; + case MENUPAGE_NEW_GAME_RELOAD: + if (m_bGameNotLoaded) + DoSettingsBeforeStartingAGame(); + break; + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_CHOOSE_SAVE_SLOT: + case MENUPAGE_CHOOSE_LOAD_SLOT: + PcSaveHelper.PopulateSlotInfo(); + break; + default: + break; + }; + + // Reset pad shaking. + if (VibrationTime != 0) { + if (CTimer::GetTimeInMillisecondsPauseMode() > VibrationTime) { + CPad::GetPad(0)->StopShaking(0); + VibrationTime = 0; + } + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::ProcessOnOffMenuOptions() { EAXJMP(0x48AE60); } +#else +void CMenuManager::ProcessOnOffMenuOptions() +{ + int8 InputDirection = (GetPadMoveLeft() || GetMouseMoveLeft()) && (!GetPadForward() && !GetMouseForward()) ? -1 : 1; + int8 InputEnter = GetPadForward(); + + uint8 NumberOfMenuOptions = GetNumberOfMenuOptions(); + + // In numerical order. + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_CHANGEMENU: + SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu); + break; + case MENUACTION_CTRLVIBRATION: + if (!m_PrefsUseVibration) + m_PrefsUseVibration = true; + + if (m_PrefsUseVibration) { + CPad::GetPad(0)->StartShake(350, 150); + VibrationTime = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + SaveSettings(); + break; + case MENUACTION_FRAMESYNC: + m_PrefsVsync = m_PrefsVsync == false; + SaveSettings(); + break; + case MENUACTION_FRAMELIMIT: + m_PrefsFrameLimiter = m_PrefsFrameLimiter == false; + SaveSettings(); + break; + case MENUACTION_TRAILS: + CMBlur::BlurOn = CMBlur::BlurOn == false; + if (CMBlur::BlurOn) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + + SaveSettings(); + break; + case MENUACTION_SUBTITLES: + m_PrefsShowSubtitles = m_PrefsShowSubtitles == false; + SaveSettings(); + break; + case MENUACTION_WIDESCREEN: +#ifndef ASPECT_RATIO_SCALE + m_PrefsUseWideScreen = m_PrefsUseWideScreen == false; +#else + if (InputDirection > 0) { + switch (m_PrefsUseWideScreen) { + case AR_AUTO: + m_PrefsUseWideScreen = AR_4_3; + break; + case AR_4_3: + m_PrefsUseWideScreen = AR_16_9; + break; + case AR_16_9: + m_PrefsUseWideScreen = AR_AUTO; + break; + }; + } + else { + switch (m_PrefsUseWideScreen) { + case AR_AUTO: + m_PrefsUseWideScreen = AR_16_9; + break; + case AR_4_3: + m_PrefsUseWideScreen = AR_AUTO; + break; + case AR_16_9: + m_PrefsUseWideScreen = AR_4_3; + break; + }; + } +#endif + SaveSettings(); + break; + case MENUACTION_BRIGHTNESS: + case MENUACTION_DRAWDIST: + case MENUACTION_MUSICVOLUME: + case MENUACTION_SFXVOLUME: + case MENUACTION_MOUSESENS: + if (InputDirection > 0) + CheckSliderMovement(1.0f); + else + CheckSliderMovement(-1.0f); + break; + case MENUACTION_RADIO: + if (InputDirection < 0) + m_PrefsRadioStation -= 1; + else + m_PrefsRadioStation += 1; + + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = HEAD_RADIO; + else if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = USERTRACK; + } + else { + if (m_PrefsRadioStation > CHATTERBOX) + m_PrefsRadioStation = HEAD_RADIO; + else if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = CHATTERBOX; + } + + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, 1); + break; + case MENUACTION_LANG_ENG: + if (m_PrefsLanguage != LANGUAGE_AMERICAN) { + m_PrefsLanguage = LANGUAGE_AMERICAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + } + break; + case MENUACTION_LANG_FRE: + if (m_PrefsLanguage != LANGUAGE_FRENCH) { + m_PrefsLanguage = LANGUAGE_FRENCH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + } + break; + case MENUACTION_LANG_GER: + if (m_PrefsLanguage != LANGUAGE_GERMAN) { + m_PrefsLanguage = LANGUAGE_GERMAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + } + break; + case MENUACTION_LANG_ITA: + if (m_PrefsLanguage != LANGUAGE_ITALIAN) { + m_PrefsLanguage = LANGUAGE_ITALIAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + } + break; + case MENUACTION_LANG_SPA: + if (m_PrefsLanguage != LANGUAGE_SPANISH) { + m_PrefsLanguage = LANGUAGE_SPANISH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + } + break; + case MENUACTION_UPDATESAVE: + PcSaveHelper.PopulateSlotInfo(); + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrOption].m_aEntries[m_nCurrOption].m_SaveSlot <= SAVESLOT_8) { + m_nCurrSaveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 2; + + SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu); + } + break; + case MENUACTION_CHECKSAVE: + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrOption].m_aEntries[m_nCurrOption].m_SaveSlot <= SAVESLOT_8) { + m_nCurrSaveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 2; + + if (Slots[m_nCurrSaveSlot] != 1 && Slots[m_nCurrSaveSlot] != 2) + SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu); + } + break; + case MENUACTION_NEWGAME: + DoSettingsBeforeStartingAGame(); + break; + case MENUACTION_SETDBGFLAG: + CTheScripts::DbgFlag = CTheScripts::DbgFlag == false; + break; + case MENUACTION_UPDATEMEMCARDSAVE: + RequestFrontEndShutdown(); + break; + case MENUACTION_INVVERT: + MousePointerStateHelper.bInvertVertically = MousePointerStateHelper.bInvertVertically == false; + return; + case MENUACTION_CANCLEGAME: + DMAudio.Service(); + RsEventHandler(rsQUITAPP, 0); + break; + case MENUACTION_RESUME: + RequestFrontEndShutdown(); + break; + case MENUACTION_SCREENRES: + if (m_bGameNotLoaded) { + if (InputEnter) { + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + m_nPrefsVideoMode = m_nDisplayVideoMode; + _psSelectScreenVM(m_nPrefsVideoMode); + CentreMousePointer(); + m_PrefsUseWideScreen = false; + SaveSettings(); + } + } + else { + char** VideoModeList = _psGetVideoModeList(); + int NumVideoModes = _psGetNumVideModes(); + + if (InputDirection > 0) { + int nCurrentVidMode = m_nDisplayVideoMode + 1; + + if (nCurrentVidMode >= NumVideoModes) + nCurrentVidMode = 0; + + while (!VideoModeList[nCurrentVidMode]) { + ++nCurrentVidMode; + + if (nCurrentVidMode >= NumVideoModes) + nCurrentVidMode = 0; + } + + m_nDisplayVideoMode = nCurrentVidMode; + } + else { + int nCurrentVidMode = m_nDisplayVideoMode - 1; + + if (nCurrentVidMode < 0) + nCurrentVidMode = NumVideoModes - 1; + + while (!VideoModeList[nCurrentVidMode]) { + --nCurrentVidMode; + + if (nCurrentVidMode < 0) + nCurrentVidMode = NumVideoModes - 1; + } + + m_nDisplayVideoMode = nCurrentVidMode; + } + } + } + break; + case MENUACTION_AUDIOHW: + { + int8 AudioHardware = m_nPrefsAudio3DProviderIndex; + if (m_nPrefsAudio3DProviderIndex == -1) + break; + + if (InputDirection > 0) { + switch (m_nPrefsAudio3DProviderIndex) { + case 0: + m_nPrefsAudio3DProviderIndex = 1; + break; + case 1: + m_nPrefsAudio3DProviderIndex = 2; + break; + case 2: + m_nPrefsAudio3DProviderIndex = 3; + break; + case 3: + m_nPrefsAudio3DProviderIndex = 4; + break; + case 4: + m_nPrefsAudio3DProviderIndex = 5; + break; + case 5: + m_nPrefsAudio3DProviderIndex = 6; + break; + case 6: + m_nPrefsAudio3DProviderIndex = 0; + break; + } + } + else { + switch (m_nPrefsAudio3DProviderIndex) { + case 0: + m_nPrefsAudio3DProviderIndex = 6; + break; + case 1: + m_nPrefsAudio3DProviderIndex = 0; + break; + case 2: + m_nPrefsAudio3DProviderIndex = 1; + break; + case 3: + m_nPrefsAudio3DProviderIndex = 2; + break; + case 4: + m_nPrefsAudio3DProviderIndex = 3; + break; + case 5: + m_nPrefsAudio3DProviderIndex = 4; + break; + case 6: + m_nPrefsAudio3DProviderIndex = 5; + break; + } + } + + DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + + if (AudioHardware == m_nPrefsAudio3DProviderIndex) + SetHelperText(0); + else + SetHelperText(4); + + SaveSettings(); + break; + } + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex == -1) + break; + + if (InputDirection > 0) { + switch (m_PrefsSpeakers) { + case 0: + m_PrefsSpeakers = 1; + break; + case 1: + m_PrefsSpeakers = 2; + break; + case 2: + m_PrefsSpeakers = 0; + break; + }; + } + else { + switch (m_PrefsSpeakers) { + case 0: + m_PrefsSpeakers = 2; + break; + case 1: + m_PrefsSpeakers = 0; + break; + case 2: + m_PrefsSpeakers = 1; + break; + }; + } + + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + break; + case MENUACTION_RESTOREDEF: + SetDefaultPreferences(m_nCurrScreen); + SetHelperText(2); + SaveSettings(); + break; + case MENUACTION_CTRLMETHOD: + if (m_ControlMethod) { + TheCamera.m_bUseMouse3rdPerson = 1; + m_ControlMethod = 0; + } + else { + TheCamera.m_bUseMouse3rdPerson = 0; + m_ControlMethod = 1; + } + SaveSettings(); + break; + case MENUACTION_DYNAMICACOUSTIC: + m_PrefsDMA = m_PrefsDMA == false; + break; + case MENUACTION_MOUSESTEER: + m_bDisableMouseSteering = m_bDisableMouseSteering == false; + return; + }; +} +#endif + +#if 0 +WRAPPER void CMenuManager::RequestFrontEndShutdown() { EAXJMP(0x488750); } +#else +void CMenuManager::RequestFrontEndShutdown() +{ + m_bShutDownFrontEndRequested = true; + DMAudio.ChangeMusicMode(1); +} +#endif + +#if 0 +WRAPPER void CMenuManager::RequestFrontEndStartUp() { EAXJMP(0x488770); } +#else +void CMenuManager::RequestFrontEndStartUp() +{ + m_bStartUpFrontEndRequested = 1; +} +#endif + +#if 0 +WRAPPER void CMenuManager::ResetHelperText() { EAXJMP(0x48B470); } +#else +void CMenuManager::ResetHelperText() +{ + m_nHelperTextMsgId = 0; + m_nHelperTextAlpha = 300; +} +#endif + +#if 0 +WRAPPER void CMenuManager::SaveLoadFileError_SetUpErrorScreen() { EAXJMP(0x488930); } +#else +void CMenuManager::SaveLoadFileError_SetUpErrorScreen() +{ + switch (PcSaveHelper.m_nHelper) { + case 1: + case 2: + case 3: + m_nPrevScreen = m_nCurrScreen; + m_nCurrScreen = MENUPAGE_SAVE_FAILED; + m_nCurrOption = MENUROW_0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + break; + break; + case 4: + case 5: + case 6: + this->m_nPrevScreen = m_nCurrScreen; + this->m_nCurrScreen = MENUPAGE_LOAD_FAILED; + m_nCurrOption = MENUROW_0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + break; + case 7: + this->m_nPrevScreen = m_nCurrScreen; + this->m_nCurrScreen = MENUPAGE_LOAD_FAILED_2; + m_nCurrOption = MENUROW_0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + break; + case 8: + case 9: + case 10: + m_nPrevScreen = m_nCurrScreen; + m_nCurrScreen = MENUPAGE_DELETE_FAILED; + m_nCurrOption = MENUROW_0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + break; + default: + return; + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::SetHelperText() { EAXJMP(0x48B450); } +#else +void CMenuManager::SetHelperText(int text) +{ + m_nHelperTextMsgId = text; + m_nHelperTextAlpha = 300; +} +#endif + +#if 0 +WRAPPER void CMenuManager::SaveSettings() { EAXJMP(0x488CC0); } +#else +void CMenuManager::SaveSettings() +{ + CFileMgr::SetDirMyDocuments(); + + int fileHandle = CFileMgr::OpenFile("gta3.set", "w"); + if (fileHandle) { + + ControlsManager.SaveSettings(fileHandle); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 20); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 20); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 4); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 4); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1); + CFileMgr::Write(fileHandle, buf("stuffmorestuffevenmorestuff etc"), 1); + CFileMgr::Write(fileHandle, buf(&TheCamera.m_bHeadBob), 1); + CFileMgr::Write(fileHandle, buf(&TheCamera.m_fMouseAccelHorzntl), 4); + CFileMgr::Write(fileHandle, buf(&TheCamera.m_fMouseAccelVertical), 4); + CFileMgr::Write(fileHandle, buf(&MousePointerStateHelper.bInvertVertically), 1); + CFileMgr::Write(fileHandle, buf(&CVehicle::m_bDisableMouseSteering), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsSfxVolume), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsMusicVolume), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsRadioStation), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsSpeakers), 1); + CFileMgr::Write(fileHandle, buf(&m_nPrefsAudio3DProviderIndex), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsDMA), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsBrightness), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsLOD), sizeof(m_PrefsLOD)); + CFileMgr::Write(fileHandle, buf(&m_PrefsShowSubtitles), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsUseWideScreen), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsVsyncDisp), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsFrameLimiter), 1); + CFileMgr::Write(fileHandle, buf(&m_nDisplayVideoMode), 1); + CFileMgr::Write(fileHandle, buf(&CMBlur::BlurOn), 1); + CFileMgr::Write(fileHandle, buf(m_PrefsSkinFile), 256); + CFileMgr::Write(fileHandle, buf(&m_ControlMethod), 1); + CFileMgr::Write(fileHandle, buf(&m_PrefsLanguage), 1); + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); +} +#endif + +#if 0 +WRAPPER void CMenuManager::ShutdownJustMenu() { EAXJMP(0x488920); } +#else +void CMenuManager::ShutdownJustMenu() +{ + m_bMenuActive = false; + CTimer::EndUserPause(); +} +#endif + +// We won't ever use this again. +#if 0 +WRAPPER float CMenuManager::StretchX(float) { EAXJMP(0x48ABE0); } +#else +float CMenuManager::StretchX(float x) +{ + if (SCREEN_WIDTH == 640) + return x; + else + return SCREEN_WIDTH * x * 0.0015625f; +} +#endif + +#if 0 +WRAPPER float CMenuManager::StretchY(float) { EAXJMP(0x48AC20); } +#else +float CMenuManager::StretchY(float y) +{ + if (SCREEN_HEIGHT == 448) + return y; + else + return SCREEN_HEIGHT * y * 0.002232143f; +} +#endif + +#if 0 +WRAPPER void CMenuManager::SwitchMenuOnAndOff() { EAXJMP(0x488790); } +#else +void CMenuManager::SwitchMenuOnAndOff() +{ + // Just what the function name says. + if (m_bShutDownFrontEndRequested || m_bStartUpFrontEndRequested) { + if (!m_bMenuActive) + m_bMenuActive = true; + + if (m_bShutDownFrontEndRequested) + m_bMenuActive = false; + if (m_bStartUpFrontEndRequested) + m_bMenuActive = true; + + if (m_bMenuActive) { + CTimer::StartUserPause(); + } + else { + ShutdownJustMenu(); + SaveSettings(); + m_bStartUpFrontEndRequested = false; + pControlEdit = 0; + m_bShutDownFrontEndRequested = false; + DisplayComboButtonErrMsg = 0; + CPad::GetPad(0)->Clear(0); + CPad::GetPad(1)->Clear(0); + SwitchToNewScreen(0); + } + } + if (m_bSaveMenuActive && !m_bQuitGameNoCD) { + m_bSaveMenuActive = false; + m_bMenuActive = true; + CTimer::StartUserPause(); + SwitchToNewScreen(MENUPAGE_CHOOSE_SAVE_SLOT); + PcSaveHelper.PopulateSlotInfo(); + } + + if (!m_bMenuActive) + field_112 = 1; + + m_bStartUpFrontEndRequested = false; + m_bShutDownFrontEndRequested = false; +} +#endif + +#if 0 +WRAPPER void CMenuManager::UnloadTextures() { EAXJMP(0x47A440); } +#else +void CMenuManager::UnloadTextures() +{ + if (m_bSpritesLoaded) { + debug("Remove frontend\n"); + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); ++i) + m_aFrontEndSprites[i].Delete(); + + int frontend = CTxdStore::FindTxdSlot("frontend"); + CTxdStore::RemoveTxdSlot(frontend); + + debug("Remove menu textures\n"); + for (int i = 0; i < ARRAY_SIZE(MenuFilenames)/2; ++i) + m_aMenuSprites[i].Delete(); + + int menu = CTxdStore::FindTxdSlot("menu"); + CTxdStore::RemoveTxdSlot(menu); + + m_bSpritesLoaded = false; + } +} +#endif + +#if 0 +WRAPPER void CMenuManager::WaitForUserCD(void) { EAXJMP(0x48ADD0); } +#else +void CMenuManager::WaitForUserCD() +{ + LoadSplash(0); + if (!RsGlobal.quit) { + HandleExit(); + CPad::UpdatePads(); + MessageScreen("NO_PCCD"); + + if (GetPadBack()) { + m_bQuitGameNoCD = true; + RsEventHandler(rsQUITAPP, 0); + } + } +} +#endif + +// New content: +uint8 CMenuManager::GetNumberOfMenuOptions() +{ + uint8 Rows = MENUROW_NONE; + for (int i = 0; i < MENUROWS; i++) { + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_NOTHING) + break; + + ++Rows; + }; + return Rows; +} + +void CMenuManager::SwitchToNewScreen(int8 screen) +{ + ResetHelperText(); + + // Return to - behaviour. + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") || + (screen == aScreens[m_nCurrScreen].m_PreviousPage[0])) { + if (m_bGameNotLoaded) { + m_nCurrOption = aScreens[m_nCurrScreen].m_ParentEntry[0]; + m_nCurrScreen = aScreens[m_nCurrScreen].m_PreviousPage[0]; + } + else { + m_nCurrOption = aScreens[m_nCurrScreen].m_ParentEntry[1]; + m_nCurrScreen = aScreens[m_nCurrScreen].m_PreviousPage[1]; + } + + m_nMenuFadeAlpha = 0; + } + else { + // Go through - behaviour. + if (screen) { + m_nPrevScreen = m_nCurrScreen; + m_nCurrScreen = screen; + m_nCurrOption = MENUROW_0; + m_nMenuFadeAlpha = 0; + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + else { + m_nPrevScreen = MENUPAGE_NONE; + m_nCurrScreen = MENUPAGE_NONE; + m_nCurrOption = MENUROW_0; + } + } + + // Set player skin. + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + CPlayerSkin::BeginFrontEndSkinEdit(); + field_535 = 19; + m_bSkinsFound = false; + } + + // Set radio station. + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, 1); + OutputDebugStringA("FRONTEND AUDIO TRACK STOPPED"); + } + else + DMAudio.StopFrontEndTrack(); +} + +void CMenuManager::SetDefaultPreferences(int8 screen) +{ + switch (screen) { + case MENUPAGE_SOUND_SETTINGS: + m_PrefsMusicVolume = 102; + m_PrefsSfxVolume = 102; + m_PrefsSpeakers = 0; + m_nPrefsAudio3DProviderIndex = 6; + m_PrefsDMA = true; + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + break; + case MENUPAGE_GRAPHICS_SETTINGS: + m_PrefsBrightness = 256; + m_PrefsFrameLimiter = true; + m_PrefsVsync = true; + m_PrefsLOD = 1.2f; + m_PrefsVsyncDisp = true; + lodMultiplier = 1.2; + CMBlur::BlurOn = true; + CMBlur::MotionBlurOpen(Scene.camera); + m_PrefsUseVibration = false; + m_PrefsShowSubtitles = true; + m_nDisplayVideoMode = m_nPrefsVideoMode; + m_PrefsUseWideScreen = false; + break; + case MENUPAGE_CONTROLLER_PC: + ControlsManager.MakeControllerActionsBlank(); + ControlsManager.InitDefaultControlConfiguration(); + + CMouseControllerState state = MousePointerStateHelper.GetMouseSetUp(); + ControlsManager.InitDefaultControlConfigMouse(state); + + if (1) { + //TODO: JoyPad stuff. + } + TheCamera.m_bUseMouse3rdPerson = 1; + m_ControlMethod = 0; + MousePointerStateHelper.bInvertVertically = true; + TheCamera.m_fMouseAccelHorzntl = 0.0025f; + TheCamera.m_fMouseAccelVertical = 0.0025f; + CVehicle::m_bDisableMouseSteering = true; + TheCamera.m_bHeadBob = false; + break; + }; +} + +// Frontend inputs. +bool GetPadBack() +{ + return + (CPad::GetPad(0)->NewKeyState.ESC && !CPad::GetPad(0)->OldKeyState.ESC) || + (CPad::GetPad(0)->NewState.Triangle && !CPad::GetPad(0)->OldState.Triangle); +} + +bool GetPadExitEnter() +{ + return + (CPad::GetPad(0)->NewKeyState.ESC && !CPad::GetPad(0)->OldKeyState.ESC) || + (CPad::GetPad(0)->NewState.Start && !CPad::GetPad(0)->OldState.Start); +} + +bool GetPadForward() +{ + return + (CPad::GetPad(0)->NewKeyState.EXTENTER && !CPad::GetPad(0)->OldKeyState.EXTENTER) || + (CPad::GetPad(0)->NewKeyState.ENTER && !CPad::GetPad(0)->OldKeyState.ENTER) || + (CPad::GetPad(0)->NewState.Cross && !CPad::GetPad(0)->OldState.Cross); +} + +bool GetPadMoveUp() +{ + return + (CPad::GetPad(0)->NewState.DPadUp && !CPad::GetPad(0)->OldState.DPadUp) || + (CPad::GetPad(0)->NewKeyState.UP && !CPad::GetPad(0)->OldKeyState.UP) || + (CPad::GetPad(0)->NewState.LeftStickY < 0 && !CPad::GetPad(0)->OldState.LeftStickY < 0); +} + +bool GetPadMoveDown() +{ + return + (CPad::GetPad(0)->NewState.DPadDown && !CPad::GetPad(0)->OldState.DPadDown) || + (CPad::GetPad(0)->NewKeyState.DOWN && !CPad::GetPad(0)->OldKeyState.DOWN) || + (CPad::GetPad(0)->NewState.LeftStickY > 0 && !CPad::GetPad(0)->OldState.LeftStickY > 0); +} + +bool GetPadMoveLeft() +{ + return + (CPad::GetPad(0)->NewState.DPadLeft && !CPad::GetPad(0)->OldState.DPadLeft) || + (CPad::GetPad(0)->NewKeyState.LEFT && !CPad::GetPad(0)->OldKeyState.LEFT) || + (CPad::GetPad(0)->NewState.LeftStickX < 0 && !CPad::GetPad(0)->OldState.LeftStickX < 0); +} + +bool GetPadMoveRight() +{ + return + (CPad::GetPad(0)->NewState.DPadRight && !CPad::GetPad(0)->OldState.DPadRight) || + (CPad::GetPad(0)->NewKeyState.RIGHT && !CPad::GetPad(0)->OldKeyState.RIGHT) || + (CPad::GetPad(0)->NewState.LeftStickX > 0 && !CPad::GetPad(0)->OldState.LeftStickX > 0); +} + +bool GetMouseForward() +{ + return + (CPad::GetPad(0)->NewMouseControllerState.LMB && !CPad::GetPad(0)->OldMouseControllerState.LMB); +} + +bool GetMouseBack() +{ + return + (CPad::GetPad(0)->NewMouseControllerState.RMB && !CPad::GetPad(0)->OldMouseControllerState.RMB); +} + +bool GetMousePos() +{ + return + (CPad::GetPad(0)->NewMouseControllerState.x != 0.0f || CPad::GetPad(0)->OldMouseControllerState.y != 0.0f); +} + +bool GetMouseMoveLeft() +{ + return + (CPad::GetPad(0)->NewMouseControllerState.WHEELDN && !CPad::GetPad(0)->OldMouseControllerState.WHEELDN != 0.0f); +} + +bool GetMouseMoveRight() +{ + return + (CPad::GetPad(0)->NewMouseControllerState.WHEELUP && !CPad::GetPad(0)->OldMouseControllerState.WHEELUP != 0.0f); +} + +bool GetPadInput() +{ + return + GetPadBack() || + GetPadForward() || + GetPadMoveUp() || + GetPadMoveDown() || + GetPadMoveLeft() || + GetPadMoveRight(); +} + +bool GetMouseInput() +{ + return + GetMouseForward() || + GetMouseBack() || + GetMousePos() || + GetMouseMoveLeft() || + GetMouseMoveRight(); +} + +STARTPATCHES + InjectHook(0x47A230, &CMenuManager::LoadAllTextures, PATCH_JUMP); + InjectHook(0x47A440, &CMenuManager::UnloadTextures, PATCH_JUMP); + InjectHook(0x485100, &CMenuManager::Process, PATCH_JUMP); + InjectHook(0x4856F0, &CMenuManager::ProcessButtonPresses, PATCH_JUMP); + InjectHook(0x48AE60, &CMenuManager::ProcessOnOffMenuOptions, PATCH_JUMP); + InjectHook(0x488EE0, &CMenuManager::LoadSettings, PATCH_JUMP); + InjectHook(0x488CC0, &CMenuManager::SaveSettings, PATCH_JUMP); + + for (int i = 1; i < ARRAY_SIZE(aScreens); i++) + Patch(0x611930 + sizeof(CMenuScreen) * i, aScreens[i]); +ENDPATCHES
\ No newline at end of file diff --git a/src/core/Frontend.h b/src/core/Frontend.h new file mode 100644 index 00000000..9b9377da --- /dev/null +++ b/src/core/Frontend.h @@ -0,0 +1,512 @@ +#pragma + +#include "Sprite2d.h" + +#define MENUHEADER_POS_X 35.0f +#define MENUHEADER_POS_Y 93.0f +#define MENUHEADER_WIDTH 0.84f +#define MENUHEADER_HEIGHT 1.6f + +#define MENUACTION_POS_X 20.0f +#define MENUACTION_POS_Y 37.5f +#define MENUACTION_WIDTH 0.675f +#define MENUACTION_HEIGHT 0.81f + +#define MENUCOLUMN_POS_X MENUHEADER_POS_X + 16.0f +#define MENUCOLUMN_MAX_Y 149.0f +#define MENUCOLUMN_MID_Y 100.0f +#define MENUCOLUMN_MIN_Y 110.0f +#define MENUCOLUMN_PAUSE_Y 25.0f +#define MENUCOLUMN_START_Y 9.0f +#define MENUCOLUMN_FEDS 139.0f + +#define MENUCOLUMN_SAVE_X 121.0f +#define MENUCOLUMN_SAVE_Y 111.0f + +#define MENUCOLUMN_SPACING_MAX 24.0f +#define MENUCOLUMN_SPACING_MIN 20.0f + +#define MENUSELECT_BOX_MAX 20.5f +#define MENUSELECT_BOX_MIN 17.0f + +#ifndef ASPECT_RATIO_SCALE +#define MENURADIO_ICON_X 31.5f +#else +#define MENURADIO_ICON_X -262.0f +#endif +#define MENURADIO_ICON_Y 29.5f +#define MENURADIO_ICON_W 60.0f +#define MENURADIO_ICON_H 60.0f + +#define MENUDROP_COLOR_A 150 +#define MENUDROP_COLOR_SIZE -1 + +#define MENUSLIDER_X 306.0f + +#define buf(a) (char*)(a) + +enum eLanguages +{ + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +}; + +enum eFrontendSprites +{ + FE2_MAINPANEL_UL, + FE2_MAINPANEL_UR, + FE2_MAINPANEL_DL, + FE2_MAINPANEL_DR, + FE2_MAINPANEL_DR2, + FE2_TABACTIVE, + FE_ICONBRIEF, + FE_ICONSTATS, + FE_ICONCONTROLS, + FE_ICONSAVE, + FE_ICONAUDIO, + FE_ICONDISPLAY, + FE_ICONLANGUAGE, + FE_CONTROLLER, + FE_CONTROLLERSH, + FE_ARROWS1, + FE_ARROWS2, + FE_ARROWS3, + FE_ARROWS4, + FE_RADIO1, + FE_RADIO2, + FE_RADIO3, + FE_RADIO4, + FE_RADIO5, + FE_RADIO6, + FE_RADIO7, + FE_RADIO8, + FE_RADIO9, +}; + +enum eMenuSprites +{ + MENUSPRITE_CONNECTION, + MENUSPRITE_FINDGAME, + MENUSPRITE_HOSTGAME, + MENUSPRITE_MAINMENU, + MENUSPRITE_PLAYERSET, + MENUSPRITE_SINGLEPLAYER, + MENUSPRITE_MULTIPLAYER, + MENUSPRITE_DMALOGO, + MENUSPRITE_GTALOGO, + MENUSPRITE_RSTARLOGO, + MENUSPRITE_GAMESPY, + MENUSPRITE_MOUSE, + MENUSPRITE_MOUSET, + MENUSPRITE_MP3LOGO, + MENUSPRITE_DOWNOFF, + MENUSPRITE_DOWNON, + MENUSPRITE_UPOFF, + MENUSPRITE_UPON, + MENUSPRITE_GTA3LOGO, +}; + +enum eSaveSlot +{ + SAVESLOT_NONE, + SAVESLOT_0, + SAVESLOT_1, + SAVESLOT_2, + SAVESLOT_3, + SAVESLOT_4, + SAVESLOT_5, + SAVESLOT_6, + SAVESLOT_7, + SAVESLOT_8, + SAVESLOT_LABEL = 36 +}; + +enum eMenuScreen +{ + MENUPAGE_DISABLED = -1, + MENUPAGE_NONE = 0, + MENUPAGE_STATS = 1, + MENUPAGE_NEW_GAME = 2, + MENUPAGE_BRIEFS = 3, + MENUPAGE_CONTROLLER_SETTINGS = 4, + MENUPAGE_SOUND_SETTINGS = 5, + MENUPAGE_GRAPHICS_SETTINGS = 6, + MENUPAGE_LANGUAGE_SETTINGS = 7, + MENUPAGE_CHOOSE_LOAD_SLOT = 8, + MENUPAGE_CHOOSE_DELETE_SLOT = 9, + MENUPAGE_NEW_GAME_RELOAD = 10, + MENUPAGE_LOAD_SLOT_CONFIRM = 11, + MENUPAGE_DELETE_SLOT_CONFIRM = 12, + MENUPAGE_13 = 13, + MENUPAGE_LOADING_IN_PROGRESS = 14, + MENUPAGE_DELETING_IN_PROGRESS = 15, + MENUPAGE_16 = 16, + MENUPAGE_DELETE_FAILED = 17, + MENUPAGE_DEBUG_MENU = 18, + MENUPAGE_MEMORY_CARD_1 = 19, + MENUPAGE_MEMORY_CARD_2 = 20, + MENUPAGE_MULTIPLAYER_MAIN = 21, + MENUPAGE_SAVE_FAILED_1 = 22, + MENUPAGE_SAVE_FAILED_2 = 23, + MENUPAGE_SAVE = 24, + MENUPAGE_NO_MEMORY_CARD = 25, + MENUPAGE_CHOOSE_SAVE_SLOT = 26, + MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27, + MENUPAGE_MULTIPLAYER_MAP = 28, + MENUPAGE_MULTIPLAYER_CONNECTION = 29, + MENUPAGE_MULTIPLAYER_FIND_GAME = 30, + MENUPAGE_MULTIPLAYER_MODE = 31, + MENUPAGE_MULTIPLAYER_CREATE = 32, + MENUPAGE_MULTIPLAYER_START = 33, + MENUPAGE_SKIN_SELECT_OLD = 34, + MENUPAGE_CONTROLLER_PC = 35, + MENUPAGE_CONTROLLER_PC_OLD1 = 36, + MENUPAGE_CONTROLLER_PC_OLD2 = 37, + MENUPAGE_CONTROLLER_PC_OLD3 = 38, + MENUPAGE_CONTROLLER_PC_OLD4 = 39, + MENUPAGE_CONTROLLER_DEBUG = 40, + MENUPAGE_OPTIONS = 41, + MENUPAGE_EXIT = 42, + MENUPAGE_SAVING_IN_PROGRESS = 43, + MENUPAGE_SAVE_SUCCESSFUL = 44, + MENUPAGE_DELETING = 45, + MENUPAGE_DELETE_SUCCESS = 46, + MENUPAGE_SAVE_FAILED = 47, + MENUPAGE_LOAD_FAILED = 48, + MENUPAGE_LOAD_FAILED_2 = 49, + MENUPAGE_FILTER_GAME = 50, + MENUPAGE_START_MENU = 51, + MENUPAGE_PAUSE_MENU = 52, + MENUPAGE_CHOOSE_MODE = 53, + MENUPAGE_SKIN_SELECT = 54, + MENUPAGE_KEYBOARD_CONTROLS = 55, + MENUPAGE_MOUSE_CONTROLS = 56, + MENUPAGE_57 = 57, + MENUPAGE_58 = 58, + MENUPAGES +}; + +enum eMenuAction +{ + MENUACTION_NOTHING, + MENUACTION_LABEL, + MENUACTION_CHANGEMENU, + MENUACTION_CTRLVIBRATION, + MENUACTION_CTRLCONFIG, + MENUACTION_CTRLDISPLAY, + MENUACTION_FRAMESYNC, + MENUACTION_FRAMELIMIT, + MENUACTION_TRAILS, + MENUACTION_SUBTITLES, + MENUACTION_WIDESCREEN, + MENUACTION_BRIGHTNESS, + MENUACTION_DRAWDIST, + MENUACTION_MUSICVOLUME, + MENUACTION_SFXVOLUME, + MENUACTION_UNK15, + MENUACTION_RADIO, + MENUACTION_LANG_ENG, + MENUACTION_LANG_FRE, + MENUACTION_LANG_GER, + MENUACTION_LANG_ITA, + MENUACTION_LANG_SPA, + MENUACTION_UPDATESAVE, + MENUACTION_CHECKSAVE, + MENUACTION_UNK24, + MENUACTION_NEWGAME, + MENUACTION_RELOADIDE, + MENUACTION_RELOADIPL, + MENUACTION_SETDBGFLAG, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, + MENUACTION_PEDROADGROUPS, + MENUACTION_CARROADGROUPS, + MENUACTION_COLLISIONPOLYS, + MENUACTION_REGMEMCARD1, + MENUACTION_TESTFORMATMEMCARD1, + MENUACTION_TESTUNFORMATMEMCARD1, + MENUACTION_CREATEROOTDIR, + MENUACTION_CREATELOADICONS, + MENUACTION_FILLWITHGUFF, + MENUACTION_SAVEONLYTHEGAME, + MENUACTION_SAVEGAME, + MENUACTION_SAVEGAMEUNDERGTA, + MENUACTION_CREATECOPYPROTECTED, + MENUACTION_TESTSAVE, + MENUACTION_TESTLOAD, + MENUACTION_TESTDELETE, + MENUACTION_PARSEHEAP, + MENUACTION_SHOWCULL, + MENUACTION_MEMCARDSAVECONFIRM, + MENUACTION_UPDATEMEMCARDSAVE, + MENUACTION_UNK50, + MENUACTION_DEBUGSTREAM, + MENUACTION_MPMAP_LIBERTY, + MENUACTION_MPMAP_REDLIGHT, + MENUACTION_MPMAP_CHINATOWN, + MENUACTION_MPMAP_TOWER, + MENUACTION_MPMAP_SEWER, + MENUACTION_MPMAP_INDUSTPARK, + MENUACTION_MPMAP_DOCKS, + MENUACTION_MPMAP_STAUNTON, + MENUACTION_MPMAP_DEATHMATCH1, + MENUACTION_MPMAP_DEATHMATCH2, + MENUACTION_MPMAP_TEAMDEATH1, + MENUACTION_MPMAP_TEAMDEATH2, + MENUACTION_MPMAP_STASH, + MENUACTION_MPMAP_CAPTURE, + MENUACTION_MPMAP_RATRACE, + MENUACTION_MPMAP_DOMINATION, + MENUACTION_STARTMP, + MENUACTION_UNK69, + MENUACTION_UNK70, + MENUACTION_FINDMP, + MENUACTION_REDEFCTRL, + MENUACTION_UNK73, + MENUACTION_INITMP, + MENUACTION_MP_PLAYERCOLOR, + MENUACTION_MP_PLAYERNAME, + MENUACTION_MP_GAMENAME, + MENUACTION_GETKEY, + MENUACTION_SHOWHEADBOB, + MENUACTION_UNK80, + MENUACTION_INVVERT, + MENUACTION_CANCLEGAME, + MENUACTION_MP_PLAYERNUMBER, + MENUACTION_MOUSESENS, + MENUACTION_CHECKMPGAMES, + MENUACTION_CHECKMPPING, + MENUACTION_MP_SERVER, + MENUACTION_MP_MAP, + MENUACTION_MP_GAMETYPE, + MENUACTION_MP_LAN, + MENUACTION_MP_INTERNET, + MENUACTION_RESUME, + MENUACTION_DONTCANCLE, + MENUACTION_SCREENRES, + MENUACTION_AUDIOHW, + MENUACTION_SPEAKERCONF, + MENUACTION_PLAYERSETUP, + MENUACTION_RESTOREDEF, + MENUACTION_CTRLMETHOD, + MENUACTION_DYNAMICACOUSTIC, + MENUACTION_LOADRADIO, + MENUACTION_MOUSESTEER, + MENUACTION_UNK103, + MENUACTION_UNK104, + MENUACTION_UNK105, + MENUACTION_UNK106, + MENUACTION_UNK107, + MENUACTION_UNK108, + MENUACTION_UNK109, + MENUACTION_UNK110, +}; + +enum eCheckHover +{ + ACTIVATE_OPTION = 2, + IGNORE_OPTION = 42, +}; + +enum eMenuColumns +{ + MENUCOLUMN_LEFT, + MENUCOLUMN_CENTER, + MENUCOLUMN_RIGHT, + MENUCOLUMNS, +}; + +enum eMenuRow +{ + MENUROW_NONE = -1, + MENUROW_0, + MENUROW_1, + MENUROW_2, + MENUROW_3, + MENUROW_4, + MENUROW_5, + MENUROW_6, + MENUROW_7, + MENUROW_8, + MENUROW_9, + MENUROW_10, + MENUROW_11, + MENUROW_12, + MENUROW_13, + MENUROW_14, + MENUROW_15, + MENUROW_16, + MENUROW_17, + MENUROWS, +}; + +struct tSkinInfo +{ + int field_0; + char skinName[256]; + char currSkinName[256]; + char date[256]; + int field_304; +}; + +struct CMenuScreen +{ + char m_ScreenName[8]; + int32 unk; + int32 m_PreviousPage[2]; // eMenuScreen + int32 m_ParentEntry[2]; // eMenuRow + + struct CMenuEntry + { + int32 m_Action; // eMenuAction + char m_EntryName[8]; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + } m_aEntries[MENUROWS]; +}; + +class CMenuManager +{ +public: + int32 m_nPrefsVideoMode; + int32 m_nDisplayVideoMode; + int8 m_nPrefsAudio3DProviderIndex; + bool m_bKeyChangeNotProcessed; + char m_aSkinName[256]; + int32 m_nHelperTextMsgId; + bool m_bLanguageLoaded; + bool m_bMenuActive; + char field_112; + char field_113; + bool m_bStartGameLoading; + bool m_bFirstTime; + bool m_bGameNotLoaded; + int32 m_nMousePosX; + int32 m_nMousePosY; + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + bool m_bShowMouse; + tSkinInfo field_12C; + tSkinInfo *m_pSelectedSkin; + tSkinInfo *field_438; + float field_43C; + int field_440; + int m_nSkinsTotal; + char _unk0[4]; + int field_44C; + bool m_bSkinsFound; + bool m_bQuitGameNoCD; + char field_452; + bool m_bSaveMenuActive; + bool m_bLoadingSavedGame; + char field_455; + char field_456; + bool m_bSpritesLoaded; + CSprite2d m_aFrontEndSprites[28]; + CSprite2d m_aMenuSprites[20]; + int field_518; + int m_nMenuFadeAlpha; + char field_520; + char field_521; + char field_522; + char field_523; + char field_524; + int m_CurrCntrlAction; + char _unk1[4]; + int field_530; + char field_534; + char field_535; + int8 field_536; + int m_nHelperTextAlpha; + int m_nMouseOldPosX; + int m_nMouseOldPosY; + int m_nHoverOption; + int m_nCurrScreen; + int m_nCurrOption; + int m_nPrevOption; + int m_nPrevScreen; + int field_558; + int m_nCurrSaveSlot; + int m_nScreenChangeDelayTimer; + + static int32 &OS_Language; + static int8 &m_PrefsUseVibration; + static int8 &m_DisplayControllerOnFoot; + static int8 &m_PrefsUseWideScreen; + static int8 &m_PrefsRadioStation; + static int8 &m_PrefsVsync; + static int8 &m_PrefsVsyncDisp; + static int8 &m_PrefsFrameLimiter; + static int8 &m_PrefsShowSubtitles; + static int8 &m_PrefsSpeakers; + static int8 &m_ControlMethod; + static int8 &m_PrefsDMA; + static int8 &m_PrefsLanguage; + static int8 &m_bDisableMouseSteering; + static int32 &m_PrefsBrightness; + static float &m_PrefsLOD; + static int8 &m_bFrontEnd_ReloadObrTxtGxt; + static int32 &m_PrefsMusicVolume; + static int32 &m_PrefsSfxVolume; + static uint8 *m_PrefsSkinFile; + + static bool &m_bStartUpFrontEndRequested; + static bool &m_bShutDownFrontEndRequested; + static bool &m_PrefsAllowNastyGame; + +public: + void BuildStatLine(char *, void *, uint16, void *); + static void CentreMousePointer(); + void CheckCodesForControls(int, int); + bool CheckHover(int x1, int x2, int y1, int y2); + void CheckSliderMovement(int); + int CostructStatLine(int); + void DisplayHelperText(); + float DisplaySlider(float, float, float, float, float, float); + void DoSettingsBeforeStartingAGame(); + void Draw(); + void DrawControllerBound(int, int, int, uint8); + void DrawControllerScreenExtraText(int, int, int); + void DrawControllerSetupScreen(); + void DrawFrontEnd(); + void DrawFrontEndNormal(); + void DrawPlayerSetupScreen(); + int FadeIn(int alpha); + void FilterOutColorMarkersFromString(uint16, CRGBA &); + int GetStartOptionsCntrlConfigScreens(); + static void InitialiseChangedLanguageSettings(); + void LoadAllTextures(); + void LoadSettings(); + static void MessageScreen(char *); + static void PickNewPlayerColour(); + void PrintBriefs(); + static void PrintErrorMessage(); + void PrintStats(); + void Process(); + void ProcessButtonPresses(); + void ProcessOnOffMenuOptions(); + static void RequestFrontEndShutdown(); + static void RequestFrontEndStartUp(); + void ResetHelperText(); + void SaveLoadFileError_SetUpErrorScreen(); + void SaveSettings(); + void SetHelperText(int text); + void ShutdownJustMenu(); + static float StretchX(float); + static float StretchY(float); + void SwitchMenuOnAndOff(); + void UnloadTextures(); + void WaitForUserCD(); + + // New content: + uint8 GetNumberOfMenuOptions(); + void SwitchToNewScreen(int8 screen); + void SetDefaultPreferences(int8 screen); + +}; + +static_assert(sizeof(CMenuManager) == 0x564, "CMenuManager: error"); + +extern CMenuManager &FrontEndMenuManager; diff --git a/src/core/Game.cpp b/src/core/Game.cpp new file mode 100644 index 00000000..cbd55c48 --- /dev/null +++ b/src/core/Game.cpp @@ -0,0 +1,23 @@ +#include "common.h" +#include "patcher.h" +#include "Game.h" + +eLevelName &CGame::currLevel = *(eLevelName*)0x941514; +bool &CGame::bDemoMode = *(bool*)0x5F4DD0; +bool &CGame::nastyGame = *(bool*)0x5F4DD4; +bool &CGame::frenchGame = *(bool*)0x95CDCB; +bool &CGame::germanGame = *(bool*)0x95CD1E; +bool &CGame::noProstitutes = *(bool*)0x95CDCF; +bool &CGame::playingIntro = *(bool*)0x95CDC2; +char *CGame::aDatFile = (char*)0x773A48; + +WRAPPER void CGame::Initialise(const char *datFile) { EAXJMP(0x48BED0); } +WRAPPER void CGame::Process(void) { EAXJMP(0x48C850); } +WRAPPER bool CGame::InitialiseOnceBeforeRW(void) { EAXJMP(0x48BB80); } +WRAPPER bool CGame::InitialiseRenderWare(void) { EAXJMP(0x48BBA0); } +WRAPPER void CGame::ShutdownRenderWare(void) { EAXJMP(0x48BCB0); } +WRAPPER void CGame::FinalShutdown(void) { EAXJMP(0x48BEC0); } +WRAPPER void CGame::ShutDown(void) { EAXJMP(0x48C3A0); } +WRAPPER void CGame::ShutDownForRestart(void) { EAXJMP(0x48C6B0); } +WRAPPER void CGame::InitialiseWhenRestarting(void) { EAXJMP(0x48C740); } +WRAPPER bool CGame::InitialiseOnceAfterRW(void) { EAXJMP(0x48BD50); } diff --git a/src/core/Game.h b/src/core/Game.h new file mode 100644 index 00000000..3bc3e633 --- /dev/null +++ b/src/core/Game.h @@ -0,0 +1,37 @@ +#pragma once + +enum eLevelName +{ + LEVEL_NONE = 0, + LEVEL_INDUSTRIAL, + LEVEL_COMMERCIAL, + LEVEL_SUBURBAN +}; + +class CGame +{ +public: + static eLevelName &currLevel; + static bool &bDemoMode; + static bool &nastyGame; + static bool &frenchGame; + static bool &germanGame; + static bool &noProstitutes; + static bool &playingIntro; + static char *aDatFile; //[32]; + + static void Initialise(const char *datFile); + static bool InitialiseOnceBeforeRW(void); + static bool InitialiseRenderWare(void); + static bool InitialiseOnceAfterRW(void); + static void InitialiseWhenRestarting(void); + static void ShutDown(void); + static void ShutdownRenderWare(void); + static void FinalShutdown(void); + static void ShutDownForRestart(void); + static void Process(void); + + // NB: these do something on PS2 + static void TidyUpMemory(bool, bool) {} + static void DrasticTidyUpMemory(void) {} +}; diff --git a/src/core/General.h b/src/core/General.h new file mode 100644 index 00000000..cae1caa0 --- /dev/null +++ b/src/core/General.h @@ -0,0 +1,91 @@ +#pragma once + +class CGeneral +{ +public: + static float GetATanOfXY(float x, float y){ + if(x == 0.0f && y == 0.0f) + return 0.0f; + float xabs = fabs(x); + float yabs = fabs(y); + + if(xabs < yabs){ + if(y > 0.0f){ + if(x > 0.0f) + return 0.5f*PI - atan2(x / y, 1.0f); + else + return 0.5f*PI + atan2(-x / y, 1.0f); + }else{ + if(x > 0.0f) + return 1.5f*PI + atan2(x / -y, 1.0f); + else + return 1.5f*PI - atan2(-x / -y, 1.0f); + } + }else{ + if(y > 0.0f){ + if(x > 0.0f) + return atan2(y / x, 1.0f); + else + return PI - atan2(y / -x, 1.0f); + }else{ + if(x > 0.0f) + return 2.0f*PI - atan2(-y / x, 1.0f); + else + return PI + atan2(-y / -x, 1.0f); + } + } + } + + static float LimitRadianAngle(float angle) + { + float result; + + if (angle < -25.0f) + result = -25.0f; + else if (angle > 25.0f) + result = 25.0f; + else + result = angle; + + while (result >= PI) { + result -= 2 * PI; + } + + while (result < -PI) { + result += 2 * PI; + } + + return result; + } + + static float GetRadianAngleBetweenPoints(float x1, float y1, float x2, float y2) + { + float x = x2 - x1; + float y = y2 - y1; + + if (y == 0.0f) + y = 0.0001f; + + if (x > 0.0f) { + if (y > 0.0f) + return PI - atan2(x / y, 1.0f); + else + return -atan2(x / y, 1.0f); + } else { + if (y > 0.0f) + return -(PI + atan2(x / y, 1.0f)); + else + return -atan2(x / y, 1.0f); + } + } + + // not too sure about all these... + static uint16 GetRandomNumber(void) + { return myrand() & MYRAND_MAX; } + // Probably don't want to ever reach high + static float GetRandomNumberInRange(float low, float high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } + + static int32 GetRandomNumberInRange(int32 low, int32 high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } +}; diff --git a/src/core/Lists.cpp b/src/core/Lists.cpp new file mode 100644 index 00000000..448a0ff1 --- /dev/null +++ b/src/core/Lists.cpp @@ -0,0 +1,26 @@ +#include "common.h" +#include "Pools.h" +#include "Lists.h" + +void* +CPtrNode::operator new(size_t){ + CPtrNode *node = CPools::GetPtrNodePool()->New(); + assert(node); + return node; +} + +void +CPtrNode::operator delete(void *p, size_t){ + CPools::GetPtrNodePool()->Delete((CPtrNode*)p); +} + +void* +CEntryInfoNode::operator new(size_t){ + CEntryInfoNode *node = CPools::GetEntryInfoNodePool()->New(); + assert(node); + return node; +} +void +CEntryInfoNode::operator delete(void *p, size_t){ + CPools::GetEntryInfoNodePool()->Delete((CEntryInfoNode*)p); +} diff --git a/src/core/Lists.h b/src/core/Lists.h new file mode 100644 index 00000000..7572e882 --- /dev/null +++ b/src/core/Lists.h @@ -0,0 +1,130 @@ +#pragma once + +class CPtrNode +{ +public: + void *item; + CPtrNode *prev; + CPtrNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CPtrList +{ +public: + CPtrNode *first; + + CPtrList(void) { first = nil; } + ~CPtrList(void) { Flush(); } + CPtrNode *FindItem(void *item){ + CPtrNode *node; + for(node = first; node; node = node->next) + if(node->item == item) + return node; + return nil; + } + CPtrNode *InsertNode(CPtrNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CPtrNode *InsertItem(void *item){ + CPtrNode *node = new CPtrNode; + node->item = item; + InsertNode(node); + return node; + } + void RemoveNode(CPtrNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CPtrNode *node){ + RemoveNode(node); + delete node; + } + void RemoveItem(void *item){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + if(node->item == item) + DeleteNode(node); + } + } + void Flush(void){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; + +class CSector; + +// This records in which sector list a Physical is +class CEntryInfoNode +{ +public: + CPtrList *list; // list in sector + CPtrNode *listnode; // node in list + CSector *sector; + + CEntryInfoNode *prev; + CEntryInfoNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CEntryInfoList +{ +public: + CEntryInfoNode *first; + + CEntryInfoList(void) { first = nil; } + ~CEntryInfoList(void) { Flush(); } + CEntryInfoNode *InsertNode(CEntryInfoNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CEntryInfoNode *InsertItem(CPtrList *list, CPtrNode *listnode, CSector *sect){ + CEntryInfoNode *node = new CEntryInfoNode; + node->list = list; + node->listnode = listnode; + node->sector = sect; + InsertNode(node); + return node; + } + void RemoveNode(CEntryInfoNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CEntryInfoNode *node){ + RemoveNode(node); + delete node; + } + void Flush(void){ + CEntryInfoNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; diff --git a/src/core/MenuScreens.h b/src/core/MenuScreens.h new file mode 100644 index 00000000..2da81f1d --- /dev/null +++ b/src/core/MenuScreens.h @@ -0,0 +1,380 @@ +#pragma once + +const CMenuScreen aScreens[] = { + // MENUPAGE_NONE = 0 + { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, }, + + // MENUPAGE_STATS = 1 + { "FET_STA", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_5, MENUROW_2, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_NEW_GAME = 2 + { "FET_SGA", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_0, MENUROW_1, + MENUACTION_CHANGEMENU, "FES_SNG", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, + MENUACTION_CHANGEMENU, "GMLOAD", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + MENUACTION_CHANGEMENU, "FES_DGA", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_BRIEFS = 3 + { "FET_BRE", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_6, MENUROW_3, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENU_CONTROLLER_SETTINGS = 4 + { "FET_CON", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_SOUND_SETTINGS = 5 + { "FET_AUD", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_1, MENUROW_1, + MENUACTION_MUSICVOLUME, "FEA_MUS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SFXVOLUME, "FEA_SFX", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_AUDIOHW, "FEA_3DH", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SPEAKERCONF, "FEA_SPK", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_RADIO, "FEA_RSS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + }, + + // MENUPAGE_GRAPHICS_SETTINGS = 6 + { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_2, MENUROW_2, + MENUACTION_BRIGHTNESS, "FED_BRI", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_DRAWDIST, "FEM_LOD", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + //MENUACTION_FRAMESYNC, "FEM_VSC", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_FRAMELIMIT, "FEM_FRM", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_TRAILS, "FED_TRA", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_SUBTITLES, "FED_SUB", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_WIDESCREEN, "FED_WIS", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_SCREENRES, "FED_RES", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LANGUAGE_SETTINGS = 7 + { "FET_LAN", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_3, MENUROW_3, + MENUACTION_LANG_ENG, "FEL_ENG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_LANG_FRE, "FEL_FRE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_LANG_GER, "FEL_GER", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_LANG_ITA, "FEL_ITA", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_LANG_SPA, "FEL_SPA", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_1, MENUROW_1, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FET_DG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_2, MENUROW_2, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 10 + { "FET_NG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FESZ_QR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_NEWGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 11 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FESZ_QL", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 12 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FESZ_QD", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_DELETING, + }, + + // MENUPAGE_13 = 13 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 14 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FED_LDW", SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 15 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FEDL_WR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_16 = 16 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FES_LOE", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_FAILED = 17 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FES_DEE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + }, + + // MENUPAGE_DEBUG_MENU = 18 + { "FED_DBG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MEMORY_CARD_1 = 19 + { "FEM_MCM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MEMORY_CARD_2 = 20 + { "FEM_MC2", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_MAIN = 21 + { "FET_MP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_SAVE_FAILED_1 = 22 + { "MCDNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVE_FAILED_2 = 23 + { "MCGNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVE = 24 + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FES_SCG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_UPDATESAVE, "GMSAVE", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + MENUACTION_UPDATEMEMCARDSAVE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_NO_MEMORY_CARD = 25 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 26 + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_UPDATEMEMCARDSAVE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_UPDATESAVE, "FEM_SL1", SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL2", SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL3", SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL4", SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL5", SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL6", SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL7", SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_UPDATESAVE, "FEM_SL8", SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FESZ_QO", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_MULTIPLAYER_MAP = 28 + { "FET_MAP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_CONNECTION = 29 + { "FET_CON", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_FIND_GAME = 30 + { "FET_FG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_MODE = 31 + { "FET_GT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_CREATE = 32 + { "FET_HG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_MULTIPLAYER_START = 33 + { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_SKIN_SELECT_OLD = 34 + { "FET_PS", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CONTROLLER_PC = 35 + { "FET_CTL", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_0, MENUROW_0, + MENUACTION_CTRLMETHOD, "FET_CME", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_CHANGEMENU, "FET_RDK", SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS, + MENUACTION_CHANGEMENU, "FET_AMS", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 = 36 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_OPTIONS = 41 + { "FET_OPT", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_1, MENUROW_4, + MENUACTION_CHANGEMENU, "FET_CTL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_CHANGEMENU, "FET_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_CHANGEMENU, "FET_DIS", SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS, + MENUACTION_CHANGEMENU, "FET_LAN", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + //MENUACTION_CHANGEMENU, "FET_PSU", SAVESLOT_NONE, MENUPAGE_SKIN_SELECT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_EXIT = 42 + { "FET_QG", MENUPAGE_NONE, MENUPAGE_NONE, MENUPAGE_NONE, MENUROW_2, MENUROW_5, + MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CANCLEGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 43 + { "", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FES_WAR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 44 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FES_SSC", SAVESLOT_LABEL, MENUPAGE_NONE, + MENUACTION_UPDATEMEMCARDSAVE, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_DELETING = 45 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FED_DLW", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_SUCCESS = 46 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "DEL_FNM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + }, + + // MENUPAGE_SAVE_FAILED = 47 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_LOAD_FAILED = 48 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LOAD_FAILED_2 = 49 + { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, MENUROW_0, MENUROW_0, + MENUACTION_LABEL, "FEC_LUN", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + }, + + // MENUPAGE_FILTER_GAME = 50 + { "FIL_FLT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_START_MENU = 51 + { "FEM_MM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, + MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT, + }, + + // MENUPAGE_PAUSE_MENU = 52 + { "FET_PAU", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + MENUACTION_RESUME, "FEM_RES", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FEP_STA", SAVESLOT_NONE, MENUPAGE_STATS, + MENUACTION_CHANGEMENU, "FEP_BRI", SAVESLOT_NONE, MENUPAGE_BRIEFS, + MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, + MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT, + }, + + // MENUPAGE_CHOOSE_MODE = 53 + { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_SKIN_SELECT = 54 + { "FET_PSU", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, MENUROW_4, MENUROW_4, + //MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 55 + { "FET_STI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_1, MENUROW_1, + //MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + }, + + // MENUPAGE_MOUSE_CONTROLS = 56 + { "FET_MTI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, MENUROW_2, MENUROW_2, + MENUACTION_MOUSESENS, "FEC_MSH", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_INVVERT, "FEC_IVV", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_MOUSESTEER, "FET_MST", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_57 = 57 + { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, + + // MENUPAGE_58 = 58 + { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUPAGE_DISABLED, MENUROW_0, MENUROW_0, + + }, +}; diff --git a/src/core/Messages.cpp b/src/core/Messages.cpp new file mode 100644 index 00000000..7fc23593 --- /dev/null +++ b/src/core/Messages.cpp @@ -0,0 +1,15 @@ +#include "common.h" +#include "patcher.h" +#include "Messages.h" + +WRAPPER void CMessages::Display(void) { EAXJMP(0x529800); } +WRAPPER void CMessages::ClearAllMessagesDisplayedByGame(void) { EAXJMP(0x52B670); } +WRAPPER int CMessages::WideStringCopy(wchar* dst, wchar* src, unsigned short size) { EAXJMP(0x5294B0); } +WRAPPER char CMessages::WideStringCompare(wchar* str1, wchar* str2, unsigned short size) { EAXJMP(0x529510); } +WRAPPER void CMessages::InsertNumberInString(wchar* src, int n1, int n2, int n3, int n4, int n5, int n6, wchar* dst) { EAXJMP(0x52A1A0); } +WRAPPER void CMessages::InsertPlayerControlKeysInString(wchar* src) { EAXJMP(0x52A490); } +WRAPPER int CMessages::GetWideStringLength(wchar* src) { EAXJMP(0x529490); } + +tPreviousBrief *CMessages::PreviousBriefs = (tPreviousBrief *)0x713C08; +tMessage *CMessages::BriefMessages = (tMessage *)0x8786E0; +tBigMessage *CMessages::BIGMessages = (tBigMessage *)0x773628; diff --git a/src/core/Messages.h b/src/core/Messages.h new file mode 100644 index 00000000..69cf117c --- /dev/null +++ b/src/core/Messages.h @@ -0,0 +1,44 @@ +#pragma once + +struct tMessage +{ + wchar *m_pText; + uint16 m_nFlag; +private: + int8 _pad6[2]; +public: + uint32 m_nTime; + uint32 m_nStartTime; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +struct tBigMessage +{ + tMessage m_Current; + tMessage m_Stack[3]; +}; + +struct tPreviousBrief +{ + wchar *m_pText; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +class CMessages +{ +public: + static tPreviousBrief *PreviousBriefs; + static tMessage *BriefMessages; + static tBigMessage *BIGMessages; + +public: + static void Display(void); + static void ClearAllMessagesDisplayedByGame(void); + static int WideStringCopy(wchar* dst, wchar* src, unsigned short size); + static char WideStringCompare(wchar* str1, wchar* str2, unsigned short size); + static void InsertNumberInString(wchar* src, int n1, int n2, int n3, int n4, int n5, int n6, wchar* dst); + static void InsertPlayerControlKeysInString(wchar* src); + static int GetWideStringLength(wchar *src); +}; diff --git a/src/core/NodeName.cpp b/src/core/NodeName.cpp new file mode 100644 index 00000000..2aea3c83 --- /dev/null +++ b/src/core/NodeName.cpp @@ -0,0 +1,77 @@ +#include "common.h" +#include "patcher.h" +#include "NodeName.h" + +static int32 &gPluginOffset = *(int32*)0x64C610; + +enum +{ + ID_NODENAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFE), +}; + +#define NODENAMEEXT(o) (RWPLUGINOFFSET(char, o, gPluginOffset)) + +void* +NodeNameConstructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + if(gPluginOffset > 0) + NODENAMEEXT(object)[0] = '\0'; + return object; +} + +void* +NodeNameDestructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + return object; +} + +void* +NodeNameCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + strncpy(NODENAMEEXT(dstObject), NODENAMEEXT(srcObject), 23); + return nil; +} + +RwStream* +NodeNameStreamRead(RwStream *stream, RwInt32 binaryLength, void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + RwStreamRead(stream, NODENAMEEXT(object), binaryLength); + NODENAMEEXT(object)[binaryLength] = '\0'; + return stream; +} + +RwStream* +NodeNameStreamWrite(RwStream *stream, RwInt32 binaryLength, const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + RwStreamWrite(stream, NODENAMEEXT(object), binaryLength); + return stream; +} + +RwInt32 +NodeNameStreamGetSize(const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + // game checks for null pointer on node name extension but that really happen + return rwstrlen(NODENAMEEXT(object)); +} + +bool +NodeNamePluginAttach(void) +{ + gPluginOffset = RwFrameRegisterPlugin(24, ID_NODENAME, + NodeNameConstructor, + NodeNameDestructor, + NodeNameCopy); + RwFrameRegisterPluginStream(ID_NODENAME, + NodeNameStreamRead, + NodeNameStreamWrite, + NodeNameStreamGetSize); + return gPluginOffset != -1; +} + +char* +GetFrameNodeName(RwFrame *frame) +{ + if(gPluginOffset < 0) + return nil; + return NODENAMEEXT(frame); +} diff --git a/src/core/NodeName.h b/src/core/NodeName.h new file mode 100644 index 00000000..1a3e057b --- /dev/null +++ b/src/core/NodeName.h @@ -0,0 +1,4 @@ +#pragma once + +bool NodeNamePluginAttach(void); +char *GetFrameNodeName(RwFrame *frame); diff --git a/src/core/PCSave.cpp b/src/core/PCSave.cpp new file mode 100644 index 00000000..628e1218 --- /dev/null +++ b/src/core/PCSave.cpp @@ -0,0 +1,20 @@ +#include "common.h" +#include "patcher.h" +#include "Frontend.h" +#include "PCSave.h" + +WRAPPER void C_PcSave::SetSaveDirectory(const char *path) { EAXJMP(0x591EA0); } +WRAPPER int8 C_PcSave::PopulateSlotInfo() { EAXJMP(0x592090); } +WRAPPER int8 C_PcSave::DeleteSlot(int) { EAXJMP(0x5922F0); } +WRAPPER int8 C_PcSave::SaveSlot(int) { EAXJMP(0x591EC0); } + +WRAPPER int8 CheckSlotDataValid(int) { EAXJMP(0x591A40); } + +WRAPPER wchar *GetNameOfSavedGame(int counter) { EAXJMP(0x591B60); } +WRAPPER wchar *GetSavedGameDateAndTime(int counter) { EAXJMP(0x591B50); } + + +C_PcSave PcSaveHelper = *(C_PcSave*)0x8E2C60; +int *Slots = (int*)0x728040; +int *SlotFileName = (int*)0x6F07C8; +int *SlotSaveDate = (int*)0x72B858; diff --git a/src/core/PCSave.h b/src/core/PCSave.h new file mode 100644 index 00000000..696e158a --- /dev/null +++ b/src/core/PCSave.h @@ -0,0 +1,21 @@ +#pragma once + +class C_PcSave +{ +public: + int32 m_nHelper; + + static void SetSaveDirectory(const char *path); + int8 PopulateSlotInfo(); + int8 DeleteSlot(int); + int8 SaveSlot(int); +}; + +extern int8 CheckSlotDataValid(int); +extern wchar *GetNameOfSavedGame(int counter); +extern wchar *GetSavedGameDateAndTime(int counter); + +extern C_PcSave PcSaveHelper; +extern int *Slots; +extern int *SlotFileName; +extern int *SlotSaveDate; diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp new file mode 100644 index 00000000..002e7180 --- /dev/null +++ b/src/core/Pad.cpp @@ -0,0 +1,2091 @@ +#pragma warning( push ) +#pragma warning( disable : 4005) +#define DIRECTINPUT_VERSION 0x0800 +#include <dinput.h> +#pragma warning( pop ) + +#include "common.h" +#include "patcher.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "Timer.h" +#include "Frontend.h" +#include "Camera.h" +#include "Game.h" +#include "CutsceneMgr.h" +#include "Font.h" +#include "Hud.h" +#include "Text.h" +#include "Timer.h" +#include "World.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Population.h" +#include "Replay.h" +#include "Weather.h" +#include "win.h" + +CPad *Pads = (CPad*)0x6F0360; // [2] +CMousePointerStateHelper &MousePointerStateHelper = *(CMousePointerStateHelper*)0x95CC8C; + +bool &CPad::bDisplayNoControllerMessage = *(bool *)0x95CD52; +bool &CPad::bObsoleteControllerMessage = *(bool *)0x95CDB8; +bool &CPad::m_bMapPadOneToPadTwo = *(bool *)0x95CD48; + +CKeyboardState &CPad::OldKeyState = *(CKeyboardState*)0x6F1E70; +CKeyboardState &CPad::NewKeyState = *(CKeyboardState*)0x6E60D0; +CKeyboardState &CPad::TempKeyState = *(CKeyboardState*)0x774DE8; + +char CPad::KeyBoardCheatString[18]; + +CMouseControllerState &CPad::OldMouseControllerState = *(CMouseControllerState*)0x8472A0; +CMouseControllerState &CPad::NewMouseControllerState = *(CMouseControllerState*)0x8809F0; +CMouseControllerState &CPad::PCTempMouseControllerState = *(CMouseControllerState*)0x6F1E60; + +_TODO("gbFastTime"); +extern bool &gbFastTime; + +WRAPPER void WeaponCheat() { EAXJMP(0x490D90); } +WRAPPER void HealthCheat() { EAXJMP(0x490E70); } +WRAPPER void TankCheat() { EAXJMP(0x490EE0); } +WRAPPER void BlowUpCarsCheat() { EAXJMP(0x491040); } +WRAPPER void ChangePlayerCheat() { EAXJMP(0x4910B0); } +WRAPPER void MayhemCheat() { EAXJMP(0x4911C0); } +WRAPPER void EverybodyAttacksPlayerCheat() { EAXJMP(0x491270); } +WRAPPER void WeaponsForAllCheat() { EAXJMP(0x491370); } +WRAPPER void FastTimeCheat() { EAXJMP(0x4913A0); } +WRAPPER void SlowTimeCheat() { EAXJMP(0x4913F0); } +WRAPPER void MoneyCheat() { EAXJMP(0x491430); } +WRAPPER void ArmourCheat() { EAXJMP(0x491460); } +WRAPPER void WantedLevelUpCheat() { EAXJMP(0x491490); } +WRAPPER void WantedLevelDownCheat() { EAXJMP(0x4914F0); } +WRAPPER void SunnyWeatherCheat() { EAXJMP(0x491520); } +WRAPPER void CloudyWeatherCheat() { EAXJMP(0x491550); } +WRAPPER void RainyWeatherCheat() { EAXJMP(0x491580); } +WRAPPER void FoggyWeatherCheat() { EAXJMP(0x4915B0); } +WRAPPER void FastWeatherCheat() { EAXJMP(0x4915E0); } +WRAPPER void OnlyRenderWheelsCheat() { EAXJMP(0x491610); } +WRAPPER void ChittyChittyBangBangCheat() { EAXJMP(0x491640); } +WRAPPER void StrongGripCheat() { EAXJMP(0x491670); } +WRAPPER void NastyLimbsCheat() { EAXJMP(0x4916A0); } +////////////////////////////////////////////////////////////////////////// + +#ifdef KANGAROO_CHEAT +void KangarooCheat() +{ + wchar *string; + CPed *playerPed = FindPlayerPed(); + int m_fMass; + + if (playerPed->m_ped_flagI80) { + string = TheText.Get("CHEATOF"); + m_fMass = 70.0f; + } else { + string = TheText.Get("CHEAT1"); + m_fMass = 15.0f; + } + CHud::SetHelpMessage(string, 1); + playerPed->m_ped_flagI80 = !playerPed->m_ped_flagI80; + + playerPed->m_fMass = m_fMass; + playerPed->m_fAirResistance = 0.4f / m_fMass; +} +#endif + +void +CControllerState::Clear(void) +{ + LeftStickX = LeftStickY = RightStickX = RightStickY = 0; + LeftShoulder1 = LeftShoulder2 = RightShoulder1 = RightShoulder2 = 0; + DPadUp = DPadDown = DPadLeft = DPadRight = 0; + Start = Select = 0; + Square = Triangle = Cross = Circle = 0; + LeftShock = RightShock = 0; + NetworkTalk = 0; +} + +void CKeyboardState::Clear() +{ + for ( int32 i = 0; i < 12; i++ ) + F[i] = 0; + + for ( int32 i = 0; i < 256; i++ ) + VK_KEYS[i] = 0; + + ESC = INS = DEL = HOME = END = PGUP = PGDN = 0; + + UP = DOWN = LEFT = RIGHT = 0; + + NUMLOCK = 0; + + DIV = MUL = SUB = ADD = 0; + + DECIMAL = NUM1 = NUM2 = NUM3 = NUM4 = 0; + + NUM5 = NUM6 = NUM7 = NUM8 = 0; + + NUM9 = NUM0 = SCROLLLOCK = PAUSE = 0; + + BACKSP = TAB = CAPSLOCK = EXTENTER = 0; + + LSHIFT = SHIFT = RSHIFT = LCTRL = RCTRL = LALT = RALT = 0; + + LWIN = RWIN = APPS = 0; +} + +void CPad::Clear(bool bResetPlayerControls) +{ + NewState.Clear(); + OldState.Clear(); + + PCTempKeyState.Clear(); + PCTempJoyState.Clear(); + PCTempMouseState.Clear(); + + NewKeyState.Clear(); + OldKeyState.Clear(); + TempKeyState.Clear(); + + NewMouseControllerState.Clear(); + OldMouseControllerState.Clear(); + PCTempMouseControllerState.Clear(); + + Phase = 0; + ShakeFreq = 0; + ShakeDur = 0; + + if ( bResetPlayerControls ) + DisablePlayerControls = false; + + bApplyBrakes = false; + + + for ( int32 i = 0; i < _TODOCONST(5); i++ ) + bHornHistory[i] = false; + + iCurrHornHistory = 0; + + for ( int32 i = 0; i < _TODOCONST(12); i++ ) + _unk[i] = ' '; + + LastTimeTouched = CTimer::GetTimeInMilliseconds(); + AverageWeapon = 0; + AverageEntries = 0; +} + +void CPad::ClearMouseHistory() +{ + PCTempMouseControllerState.Clear(); + NewMouseControllerState.Clear(); + OldMouseControllerState.Clear(); +} + +CMouseControllerState::CMouseControllerState() +{ + LMB = 0; + RMB = 0; + MMB = 0; + WHEELUP = 0; + WHEELDN = 0; + MXB1 = 0; + MXB2 = 0; + + x = 0.0f; + y = 0.0f; +} + +void CMouseControllerState::Clear() +{ + LMB = 0; + RMB = 0; + MMB = 0; + WHEELUP = 0; + WHEELDN = 0; + MXB1 = 0; + MXB2 = 0; +} + +CMouseControllerState CMousePointerStateHelper::GetMouseSetUp() +{ + CMouseControllerState state; + + if ( PSGLOBAL(mouse) == nil ) + _InputInitialiseMouse(); + + if ( PSGLOBAL(mouse) != nil ) + { + DIDEVCAPS devCaps; + devCaps.dwSize = sizeof(DIDEVCAPS); + + PSGLOBAL(mouse)->GetCapabilities(&devCaps); + + switch ( devCaps.dwButtons ) + { + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + state.MMB = true; + + case 2: + state.RMB = true; + + case 1: + state.LMB = true; + } + + if ( devCaps.dwAxes == 3 ) + { + state.WHEELDN = true; + state.WHEELUP = true; + } + } + + return state; +} + +void CPad::UpdateMouse() +{ + if ( IsForegroundApp() ) + { + if ( PSGLOBAL(mouse) == nil ) + _InputInitialiseMouse(); + + DIMOUSESTATE2 state; + + if ( PSGLOBAL(mouse) != nil && SUCCEEDED(_InputGetMouseState(&state)) ) + { + int32 signX = 1; + int32 signy = 1; + + if ( !FrontEndMenuManager.m_bMenuActive ) + { + if ( MousePointerStateHelper.bInvertVertically ) + signy = -1; + if ( MousePointerStateHelper.bInvertHorizontally ) + signX = -1; + } + + PCTempMouseControllerState.Clear(); + + PCTempMouseControllerState.x = (float)(signX * state.lX); + PCTempMouseControllerState.y = (float)(signy * state.lY); + PCTempMouseControllerState.LMB = state.rgbButtons[0] & 128; + PCTempMouseControllerState.RMB = state.rgbButtons[1] & 128; + PCTempMouseControllerState.MMB = state.rgbButtons[2] & 128; + PCTempMouseControllerState.MXB1 = state.rgbButtons[3] & 128; + PCTempMouseControllerState.MXB2 = state.rgbButtons[4] & 128; + + if ( state.lZ > 0 ) + PCTempMouseControllerState.WHEELUP = 1; + else if ( state.lZ < 0 ) + PCTempMouseControllerState.WHEELDN = 1; + + OldMouseControllerState = NewMouseControllerState; + NewMouseControllerState = PCTempMouseControllerState; + } + } +} + +CControllerState CPad::ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2) +{ + static CControllerState ReconState; + + ReconState.Clear(); + +#define _RECONCILE_BUTTON(button) \ + { if ( State1.button || State2.button ) ReconState.button = 255; } + +#define _RECONCILE_AXIS_POSITIVE(axis) \ + { if ( State1.axis >= 0 && State2.axis >= 0 ) ReconState.axis = max(State1.axis, State2.axis); } + +#define _RECONCILE_AXIS_NEGATIVE(axis) \ + { if ( State1.axis <= 0 && State2.axis <= 0 ) ReconState.axis = min(State1.axis, State2.axis); } + +#define _RECONCILE_AXIS(axis) \ + { _RECONCILE_AXIS_POSITIVE(axis); _RECONCILE_AXIS_NEGATIVE(axis); } + +#define _FIX_AXIS_DIR(axis) \ + { if ( State1.axis > 0 && State2.axis < 0 || State1.axis < 0 && State2.axis > 0 ) ReconState.axis = 0; } + +#define _FIX_RECON_DIR(pos, neg, axis) \ + { if ( (ReconState.pos || ReconState.axis < 0) && (ReconState.neg || ReconState.axis > 0) ) { ReconState.pos = 0; ReconState.neg = 0; ReconState.axis = 0; } } + + _RECONCILE_BUTTON(LeftShoulder1); + _RECONCILE_BUTTON(LeftShoulder2); + _RECONCILE_BUTTON(RightShoulder1); + _RECONCILE_BUTTON(RightShoulder2); + _RECONCILE_BUTTON(Start); + _RECONCILE_BUTTON(Select); + _RECONCILE_BUTTON(Square); + _RECONCILE_BUTTON(Triangle); + _RECONCILE_BUTTON(Cross); + _RECONCILE_BUTTON(Circle); + _RECONCILE_BUTTON(LeftShock); + _RECONCILE_BUTTON(RightShock); + _RECONCILE_BUTTON(NetworkTalk); + _RECONCILE_AXIS(LeftStickX); + _RECONCILE_AXIS(LeftStickY); + _FIX_AXIS_DIR(LeftStickX); + _FIX_AXIS_DIR(LeftStickY); + _RECONCILE_AXIS(RightStickX); + _RECONCILE_AXIS(RightStickY); + _FIX_AXIS_DIR(RightStickX); + _FIX_AXIS_DIR(RightStickY); + _RECONCILE_BUTTON(DPadUp); + _RECONCILE_BUTTON(DPadDown); + _RECONCILE_BUTTON(DPadLeft); + _RECONCILE_BUTTON(DPadRight); + _FIX_RECON_DIR(DPadUp, DPadDown, LeftStickY); + _FIX_RECON_DIR(DPadLeft, DPadRight, LeftStickX); + + return ReconState; + +#undef _RECONCILE_BUTTON +#undef _RECONCILE_AXIS_POSITIVE +#undef _RECONCILE_AXIS_NEGATIVE +#undef _RECONCILE_AXIS +#undef _FIX_AXIS_DIR +#undef _FIX_RECON_DIR +} + +void CPad::StartShake(int16 nDur, uint8 nFreq) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + if ( nFreq == 0 ) + { + ShakeDur = 0; + ShakeFreq = 0; + return; + } + + if ( nDur > ShakeDur ) + { + ShakeDur = nDur; + ShakeFreq = nFreq; + } +} + +void CPad::StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fZ) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, fZ) ).Magnitude(); + + if ( fDist < 70.0f ) + { + if ( nFreq == 0 ) + { + ShakeDur = 0; + ShakeFreq = 0; + return; + } + + if ( nDur > ShakeDur ) + { + ShakeDur = nDur; + ShakeFreq = nFreq; + } + } +} + +void CPad::StartShake_Train(float fX, float fY) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + if (FindPlayerVehicle() != nil && FindPlayerVehicle()->IsTrain() ) + return; + + float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, 0.0f) ).Magnitude2D(); + + if ( fDist < 70.0f ) + { + int32 freq = (int32)((70.0f - fDist) * 70.0f / 70.0f + 30.0f); + + if ( ShakeDur < 100 ) + { + ShakeDur = 100; + ShakeFreq = freq; + } + } +} + +void CPad::AddToPCCheatString(char c) +{ + for ( int32 i = ARRAY_SIZE(KeyBoardCheatString); i >= 0; i-- ) + KeyBoardCheatString[i + 1] = KeyBoardCheatString[i]; + + KeyBoardCheatString[0] = c; + + #define _CHEATCMP(str) strncmp(str, KeyBoardCheatString, sizeof(str)-1) + + // "GUNSGUNSGUNS" + if ( !_CHEATCMP("SNUGSNUGSNUG") ) + WeaponCheat(); + + // "IFIWEREARICHMAN" + if ( !_CHEATCMP("NAMHCIRAEREWIFI") ) + MoneyCheat(); + + // "GESUNDHEIT" + if ( !_CHEATCMP("TIEHDNUSEG") ) + HealthCheat(); + + // "MOREPOLICEPLEASE" + if ( !_CHEATCMP("ESAELPECILOPEROM") ) + WantedLevelUpCheat(); + + // "NOPOLICEPLEASE" + if ( !_CHEATCMP("ESAELPECILOPON") ) + WantedLevelDownCheat(); + + // "GIVEUSATANK" + if ( !_CHEATCMP("KNATASUEVIG") ) + TankCheat(); + + // "BANGBANGBANG" + if ( !_CHEATCMP("GNABGNABGNAB") ) + BlowUpCarsCheat(); + + // "ILIKEDRESSINGUP" + if ( !_CHEATCMP("PUGNISSERDEKILI") ) + ChangePlayerCheat(); + + // "ITSALLGOINGMAAAD" + if ( !_CHEATCMP("DAAAMGNIOGLLASTI") ) + MayhemCheat(); + + // "NOBODYLIKESME" + if ( !_CHEATCMP("EMSEKILYDOBON") ) + EverybodyAttacksPlayerCheat(); + + // "WEAPONSFORALL" + if ( !_CHEATCMP("LLAROFSNOPAEW") ) + WeaponsForAllCheat(); + + // "TIMEFLIESWHENYOU" + if ( !_CHEATCMP("UOYNEHWSEILFEMIT") ) + FastTimeCheat(); + + // "BOOOOORING" + if ( !_CHEATCMP("GNIROOOOOB") ) + SlowTimeCheat(); + +#ifndef GTA3_1_1_PATCH + // "TURTOISE" + if ( !_CHEATCMP("ESIOTRUT") ) + ArmourCheat(); +#else + // "TORTOISE" + if ( !_CHEATCMP("ESIOTROT") ) + ArmourCheat(); +#endif + + // "SKINCANCERFORME" + if ( !_CHEATCMP("EMROFRECNACNIKS") ) + SunnyWeatherCheat(); + + // "ILIKESCOTLAND" + if ( !_CHEATCMP("DNALTOCSEKILI") ) + CloudyWeatherCheat(); + + // "ILOVESCOTLAND" + if ( !_CHEATCMP("DNALTOCSEVOLI") ) + RainyWeatherCheat(); + + // "PEASOUP" + if ( !_CHEATCMP("PUOSAEP") ) + FoggyWeatherCheat(); + + // "MADWEATHER" + if ( !_CHEATCMP("REHTAEWDAM") ) + FastWeatherCheat(); + + // "ANICESETOFWHEELS" + if ( !_CHEATCMP("SLEEHWFOTESECINA") ) + OnlyRenderWheelsCheat(); + + // "CHITTYCHITTYBB" + if ( !_CHEATCMP("BBYTTIHCYTTIHC") ) + ChittyChittyBangBangCheat(); + + // "CORNERSLIKEMAD" + if ( !_CHEATCMP("DAMEKILSRENROC") ) + StrongGripCheat(); + + // "NASTYLIMBSCHEAT" + if ( !_CHEATCMP("TAEHCSBMILYTSAN") ) + NastyLimbsCheat(); + +#ifdef KANGAROO_CHEAT + // "KANGAROO" + if (!_CHEATCMP("OORAGNAK")) + KangarooCheat(); +#endif + + #undef _CHEATCMP +} + +void CPad::UpdatePads(void) +{ + bool bUpdate = true; + + GetPad(0)->UpdateMouse(); + CapturePad(0); + + + ControlsManager.ClearSimButtonPressCheckers(); + ControlsManager.AffectPadFromKeyBoard(); + ControlsManager.AffectPadFromMouse(); + + if ( CReplay::IsPlayingBackFromFile() ) + bUpdate = false; + + if ( bUpdate ) + { + GetPad(0)->Update(0); + } + + GetPad(1)->NewState.Clear(); + GetPad(1)->OldState.Clear(); + + OldKeyState = NewKeyState; + NewKeyState = TempKeyState; +} + +void CPad::ProcessPCSpecificStuff(void) +{ + ; +} + +void CPad::Update(int16 unk) +{ + OldState = NewState; + + NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState); + NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState); + + PCTempJoyState.Clear(); + PCTempKeyState.Clear(); + PCTempMouseState.Clear(); + + ProcessPCSpecificStuff(); + + if ( ++iCurrHornHistory >= _TODOCONST(5) ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; +} + +void CPad::DoCheats(void) +{ + GetPad(0)->DoCheats(0); +} + +void CPad::DoCheats(int16 unk) +{ +#ifdef PS2 + if ( GetTriangleJustDown() ) + AddToCheatString('T'); + + if ( GetCircleJustDown() ) + AddToCheatString('C'); + + if ( GetCrossJustDown() ) + AddToCheatString('X'); + + if ( GetSquareJustDown() ) + AddToCheatString('S'); + + if ( GetDPadUpJustDown() ) + AddToCheatString('U'); + + if ( GetDPadDownJustDown() ) + AddToCheatString('D'); + + if ( GetDPadLeftJustDown() ) + AddToCheatString('L'); + + if ( GetDPadRightJustDown() ) + AddToCheatString('R'); + + if ( GetLeftShoulder1JustDown() ) + AddToCheatString('1'); + + if ( GetLeftShoulder2JustDown() ) + AddToCheatString('2'); + + if ( GetRightShoulder1JustDown() ) + AddToCheatString('3'); + + if ( GetRightShoulder2JustDown() ) + AddToCheatString('4'); +#endif +} + +void CPad::StopPadsShaking(void) +{ + GetPad(0)->StopShaking(0); +} + +void CPad::StopShaking(int16 unk) +{ + ; +} + +CPad *CPad::GetPad(int32 pad) +{ + return &Pads[pad]; +} + +int16 CPad::GetSteeringLeftRight(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickX; + + break; + } + } + + return 0; +} + +int16 CPad::GetSteeringUpDown(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +int16 CPad::GetCarGunUpDown(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickY; + + break; + } + + case 3: + { + return (NewState.DPadUp - NewState.DPadDown) / 2; + + break; + } + } + + return 0; +} + +int16 CPad::GetCarGunLeftRight(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickX; + + break; + } + + case 3: + { + return (NewState.DPadRight - NewState.DPadLeft) / 2; + + break; + } + } + + return 0; +} + +int16 CPad::GetPedWalkLeftRight(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickX; + + break; + } + } + + return 0; +} + + +int16 CPad::GetPedWalkUpDown(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +int16 CPad::GetAnalogueUpDown(void) +{ + switch ( Mode ) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +bool CPad::GetLookLeft(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.LeftShoulder2 && !NewState.RightShoulder2); +} + +bool CPad::GetLookRight(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.RightShoulder2 && !NewState.LeftShoulder2); +} + + +bool CPad::GetLookBehindForCar(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.RightShoulder2 && NewState.LeftShoulder2); +} + +bool CPad::GetLookBehindForPed(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!NewState.RightShock; +} + +bool CPad::GetHorn(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + { + return !!NewState.LeftShock; + + break; + } + + case 1: + { + return !!NewState.LeftShoulder1; + + break; + } + + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShock; + + break; + } + } + + return false; +} + +bool CPad::HornJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + + case 1: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + + case 2: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + + case 3: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + } + + return false; +} + + +bool CPad::GetCarGunFired(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return !!NewState.Circle; + + break; + } + + case 3: + { + return !!NewState.RightShoulder1; + + break; + } + } + + return false; +} + +bool CPad::CarGunJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + } + + return false; +} + +int16 CPad::GetHandBrake(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 1: + { + return NewState.RightShoulder1; + + break; + } + + case 2: + { + return NewState.Triangle; + + break; + } + + case 3: + { + return NewState.LeftShoulder1; + + break; + } + } + + return 0; +} + +int16 CPad::GetBrake(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + return NewState.Square; + + break; + } + + case 1: + { + return NewState.Square; + + break; + } + + case 3: + { + int16 axis = 2 * NewState.RightStickY; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } + } + + return 0; +} + +bool CPad::GetExitVehicle(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 3: + { + return !!NewState.Triangle; + + break; + } + + case 2: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::ExitVehicleJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 3: + { + return !!(NewState.Triangle && !OldState.Triangle); + + break; + } + + case 2: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + } + + return false; +} + +int32 CPad::GetWeapon(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + { + return NewState.Circle; + + break; + } + + case 2: + { + return NewState.Cross; + + break; + } + + case 3: + { + return NewState.RightShoulder1; + + break; + } + } + + return false; +} + +bool CPad::WeaponJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + + case 2: + { + return !!(NewState.Cross && !OldState.Cross); + + break; + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + } + + return false; +} + +int16 CPad::GetAccelerate(void) +{ + if ( DisablePlayerControls ) + return 0; + + switch ( Mode ) + { + case 0: + case 2: + { + return NewState.Cross; + + break; + } + + case 1: + { + return NewState.Cross; + + break; + } + + case 3: + { + int16 axis = -2 * NewState.RightStickY; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } + } + + return 0; +} + +bool CPad::CycleCameraModeUpJustDown(void) +{ + switch ( Mode ) + { + case 0: + case 2: + case 3: + { + return !!(NewState.Select && !OldState.Select); + + break; + } + + case 1: + { + return !!(NewState.DPadUp && !OldState.DPadUp); + + break; + } + } + + return false; +} + +bool CPad::CycleCameraModeDownJustDown(void) +{ + switch ( Mode ) + { + case 0: + case 2: + case 3: + { + return false; + + break; + } + + case 1: + { + return !!(NewState.DPadDown && !OldState.DPadDown); + + break; + } + } + + return false; +} + +bool CPad::ChangeStationJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + + case 1: + { + return !!(NewState.Select && !OldState.Select); + + break; + } + + case 2: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + + case 3: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + } + + return false; +} + + +bool CPad::CycleWeaponLeftJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::CycleWeaponRightJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +bool CPad::GetTarget(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::TargetJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 2: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + + case 3: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + } + + return false; +} + +bool CPad::JumpJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.Square && !OldState.Square); +} + +bool CPad::GetSprint(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Circle; + + break; + } + } + + return false; +} + +bool CPad::ShiftTargetLeftJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::ShiftTargetRightJustDown(void) +{ + if ( DisablePlayerControls ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +bool CPad::GetAnaloguePadUp(void) +{ + static int16 oldfStickY = 0; + + int16 Y = CPad::GetPad(0)->GetAnalogueUpDown(); + + if ( Y < 0 && oldfStickY >= 0 ) + { + oldfStickY = Y; + return true; + } + else + { + oldfStickY = Y; + return false; + } +} + +bool CPad::GetAnaloguePadDown(void) +{ + static int16 oldfStickY = 0; + + int16 Y = CPad::GetPad(0)->GetAnalogueUpDown(); + + if ( Y > 0 && oldfStickY <= 0 ) + { + oldfStickY = Y; + return true; + } + else + { + oldfStickY = Y; + return false; + } +} + +bool CPad::GetAnaloguePadLeft(void) +{ + static int16 oldfStickX = 0; + + int16 X = CPad::GetPad(0)->GetPedWalkLeftRight(); + + if ( X < 0 && oldfStickX >= 0 ) + { + oldfStickX = X; + return true; + } + else + { + oldfStickX = X; + return false; + } +} + +bool CPad::GetAnaloguePadRight(void) +{ + static int16 oldfStickX = 0; + + int16 X = CPad::GetPad(0)->GetPedWalkLeftRight(); + + if ( X > 0 && oldfStickX <= 0 ) + { + oldfStickX = X; + return true; + } + else + { + oldfStickX = X; + return false; + } +} + +bool CPad::GetAnaloguePadLeftJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX < 0 ) + { + oldfStickX = X; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +bool CPad::GetAnaloguePadRightJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX > 0 ) + { + oldfStickX = X; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +bool CPad::ForceCameraBehindPlayer(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + { + return !!NewState.LeftShoulder1; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + + case 3: + { + return !!NewState.Circle; + + break; + } + } + + return false; +} + +bool CPad::SniperZoomIn(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 3: + { + return !!NewState.Square; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + } + + return false; +} + +bool CPad::SniperZoomOut(void) +{ + if ( DisablePlayerControls ) + return false; + + switch ( Mode ) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Square; + + break; + } + } + + return false; +} + + +int16 CPad::SniperModeLookLeftRight(void) +{ + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; +} + +int16 CPad::SniperModeLookUpDown(void) +{ + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + + if ( abs(axis) > abs(dpad) ) + return axis; + else + return dpad; +} + +int16 CPad::LookAroundLeftRight(void) +{ + float axis = GetPad(0)->NewState.RightStickX; + + if ( fabs(axis) > 85 && !GetLookBehindForPed() ) + return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) ) + * (127.0f / 32.0f) ); // 3.96875f + + else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && fabs(axis) > 10 ) + return (int16) ( (axis + ( ( axis > 0 ) ? -10 : 10) ) + * (127.0f / 64.0f) ); // 1.984375f + + return 0; +} + +int16 CPad::LookAroundUpDown(void) +{ + int16 axis = GetPad(0)->NewState.RightStickY; + + if ( abs(axis) > 85 && !GetLookBehindForPed() ) + return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) ) + * (127.0f / 32.0f) ); // 3.96875f + + else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && abs(axis) > 40 ) + return (int16) ( (axis + ( ( axis > 0 ) ? -40 : 40) ) + * (127.0f / 64.0f) ); // 1.984375f + + return 0; +} + + +void CPad::ResetAverageWeapon(void) +{ + AverageWeapon = GetWeapon(); + AverageEntries = 1; +} + +void CPad::PrintErrorMessage(void) +{ + if ( bDisplayNoControllerMessage && !CGame::playingIntro && !FrontEndMenuManager.m_bMenuActive ) + { + CFont::SetScale(0.85f, 1.0f); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreSize(SCREEN_WIDTH - 20); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2, + TheText.Get("NOCONT") // Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + ); + } + else if ( bObsoleteControllerMessage ) + { + CFont::SetScale(0.85f, 1.0f); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreSize(SCREEN_WIDTH - 20); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2, + TheText.Get("WRCONT") // The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + ); + } + +} + +void LittleTest(void) +{ + static int32 Cunt = 0; + + Cunt++; // ??? +} + +void CPad::ResetCheats(void) +{ + CWeather::ReleaseWeather(); + + CPopulation::ms_bGivePedsWeapons = false; + + CPed::bNastyLimbsCheat = false; + CPed::bPedCheat2 = false; + CPed::bPedCheat3 = false; + + CVehicle::bWheelsOnlyCheat = false; + CVehicle::bAllDodosCheat = false; + CVehicle::bCheat3 = false; + CVehicle::bCheat4 = false; + CVehicle::bCheat5 = false; + + gbFastTime = false; + CTimer::SetTimeScale(1.0f); +} + +char *CPad::EditString(char *pStr, int32 nSize) +{ + int32 pos = strlen(pStr); + + // letters + for ( int32 i = 0; i < ('Z' - 'A' + 1); i++ ) + { + if ( GetPad(0)->GetCharJustDown(i + 'A') && pos < nSize - 1 ) + { + pStr[pos++] = i + 'A'; + pStr[pos] = '\0'; + } + + if ( GetPad(0)->GetCharJustDown(i + 'a') && pos < nSize - 1 ) + { + pStr[pos++] = i + 'a'; + pStr[pos] = '\0'; + } + } + + // numbers + for ( int32 i = 0; i < ('0' - '9' + 1); i++ ) + { + if ( GetPad(0)->GetCharJustDown(i + '0') && pos < nSize - 1 ) + { + pStr[pos++] = i + '0'; + pStr[pos] = '\0'; + } + } + + // space + if ( GetPad(0)->GetCharJustDown(' ') && pos < nSize - 1 ) + { + pStr[pos++] = ' '; + pStr[pos] = '\0'; + } + + + // del + if ( GetPad(0)->GetDeleteJustDown() || GetPad(0)->GetBackspaceJustDown() ) + { + if ( pos > 0 ) + pStr[pos - 1] = '\0'; + } + + // extenter/up/down + if ( GetPad(0)->GetEnterJustDown() || GetPad(0)->GetUpJustDown() || GetPad(0)->GetDownJustDown() ) + return nil; + + return pStr; +} + +int32 *CPad::EditCodesForControls(int32 *pRsKeys, int32 nSize) +{ + *pRsKeys = rsNULL; + + for ( int32 i = 0; i < 255; i++ ) + { + if ( GetPad(0)->GetCharJustDown(i) ) + *pRsKeys = i; + } + + for ( int32 i = 0; i < 12; i++ ) + { + if ( GetPad(0)->GetFJustDown(i) ) + *pRsKeys = i + rsF1; + } + + if ( GetPad(0)->GetEscapeJustDown() ) + *pRsKeys = rsESC; + + if ( GetPad(0)->GetInsertJustDown() ) + *pRsKeys = rsINS; + + if ( GetPad(0)->GetDeleteJustDown() ) + *pRsKeys = rsDEL; + + if ( GetPad(0)->GetHomeJustDown() ) + *pRsKeys = rsHOME; + + if ( GetPad(0)->GetEndJustDown() ) + *pRsKeys = rsEND; + + if ( GetPad(0)->GetPageUpJustDown() ) + *pRsKeys = rsPGUP; + + if ( GetPad(0)->GetPageDownJustDown() ) + *pRsKeys = rsPGDN; + + if ( GetPad(0)->GetUpJustDown() ) + *pRsKeys = rsUP; + + if ( GetPad(0)->GetDownJustDown() ) + *pRsKeys = rsDOWN; + + if ( GetPad(0)->GetLeftJustDown() ) + *pRsKeys = rsLEFT; + + if ( GetPad(0)->GetRightJustDown() ) + *pRsKeys = rsRIGHT; + + if ( GetPad(0)->GetScrollLockJustDown() ) + *pRsKeys = rsSCROLL; + + if ( GetPad(0)->GetPauseJustDown() ) + *pRsKeys = rsPAUSE; + + if ( GetPad(0)->GetNumLockJustDown() ) + *pRsKeys = rsNUMLOCK; + + if ( GetPad(0)->GetDivideJustDown() ) + *pRsKeys = rsDIVIDE; + + if ( GetPad(0)->GetTimesJustDown() ) + *pRsKeys = rsTIMES; + + if ( GetPad(0)->GetMinusJustDown() ) + *pRsKeys = rsMINUS; + + if ( GetPad(0)->GetPlusJustDown() ) + *pRsKeys = rsPLUS; + + if ( GetPad(0)->GetPadEnterJustDown() ) + *pRsKeys = rsPADENTER; + + if ( GetPad(0)->GetPadDelJustDown() ) + *pRsKeys = rsPADDEL; + + if ( GetPad(0)->GetPad1JustDown() ) + *pRsKeys = rsPADEND; + + if ( GetPad(0)->GetPad2JustDown() ) + *pRsKeys = rsPADDOWN; + + if ( GetPad(0)->GetPad3JustDown() ) + *pRsKeys = rsPADPGDN; + + if ( GetPad(0)->GetPad4JustDown() ) + *pRsKeys = rsPADLEFT; + + if ( GetPad(0)->GetPad5JustDown() ) + *pRsKeys = rsPAD5; + + if ( GetPad(0)->GetPad6JustDown() ) + *pRsKeys = rsPADRIGHT; + + if ( GetPad(0)->GetPad7JustDown() ) + *pRsKeys = rsPADHOME; + + if ( GetPad(0)->GetPad8JustDown() ) + *pRsKeys = rsPADUP; + + if ( GetPad(0)->GetPad9JustDown() ) + *pRsKeys = rsPADPGUP; + + if ( GetPad(0)->GetPad0JustDown() ) + *pRsKeys = rsPADINS; + + if ( GetPad(0)->GetBackspaceJustDown() ) + *pRsKeys = rsBACKSP; + + if ( GetPad(0)->GetTabJustDown() ) + *pRsKeys = rsTAB; + + if ( GetPad(0)->GetCapsLockJustDown() ) + *pRsKeys = rsCAPSLK; + + if ( GetPad(0)->GetEnterJustDown() ) + *pRsKeys = rsENTER; + + if ( GetPad(0)->GetLeftShiftJustDown() ) + *pRsKeys = rsLSHIFT; + + if ( GetPad(0)->GetShiftJustDown() ) + *pRsKeys = rsSHIFT; + + if ( GetPad(0)->GetRightShiftJustDown() ) + *pRsKeys = rsRSHIFT; + + if ( GetPad(0)->GetLeftCtrlJustDown() ) + *pRsKeys = rsLCTRL; + + if ( GetPad(0)->GetRightCtrlJustDown() ) + *pRsKeys = rsRCTRL; + + if ( GetPad(0)->GetLeftAltJustDown() ) + *pRsKeys = rsLALT; + + if ( GetPad(0)->GetRightAltJustDown() ) + *pRsKeys = rsRALT; + + if ( GetPad(0)->GetLeftWinJustDown() ) + *pRsKeys = rsLWIN; + + if ( GetPad(0)->GetRightWinJustDown() ) + *pRsKeys = rsRWIN; + + if ( GetPad(0)->GetAppsJustDown() ) + *pRsKeys = rsAPPS; + + return pRsKeys; +} + +STARTPATCHES + InjectHook(0x4916C0, &CControllerState::Clear, PATCH_JUMP); + InjectHook(0x491760, &CKeyboardState::Clear, PATCH_JUMP); + InjectHook(0x491A10, &CPad::Clear, PATCH_JUMP); + InjectHook(0x491B50, &CPad::ClearMouseHistory, PATCH_JUMP); + //InjectHook(0x491B80, &CMouseControllerState::CMouseControllerState, PATCH_JUMP); + InjectHook(0x491BB0, &CMouseControllerState::Clear, PATCH_JUMP); + InjectHook(0x491BD0, &CMousePointerStateHelper::GetMouseSetUp, PATCH_JUMP); + InjectHook(0x491CA0, &CPad::UpdateMouse, PATCH_JUMP); + InjectHook(0x491E60, &CPad::ReconcileTwoControllersInput, PATCH_JUMP); + InjectHook(0x492230, &CPad::StartShake, PATCH_JUMP); + InjectHook(0x492290, &CPad::StartShake_Distance, PATCH_JUMP); + InjectHook(0x492360, &CPad::StartShake_Train, PATCH_JUMP); + InjectHook(0x492450, &CPad::AddToPCCheatString, PATCH_JUMP); + InjectHook(0x492720, CPad::UpdatePads, PATCH_JUMP); + InjectHook(0x492C60, &CPad::ProcessPCSpecificStuff, PATCH_JUMP); + InjectHook(0x492C70, &CPad::Update, PATCH_JUMP); +#pragma warning( push ) +#pragma warning( disable : 4573) + InjectHook(0x492F00, (void (*)())CPad::DoCheats, PATCH_JUMP); +#pragma warning( pop ) + InjectHook(0x492F20, (void (CPad::*)(int16))&CPad::DoCheats, PATCH_JUMP); + InjectHook(0x492F30, CPad::StopPadsShaking, PATCH_JUMP); + InjectHook(0x492F50, &CPad::StopShaking, PATCH_JUMP); + InjectHook(0x492F60, CPad::GetPad, PATCH_JUMP); + InjectHook(0x492F70, &CPad::GetSteeringLeftRight, PATCH_JUMP); + InjectHook(0x492FF0, &CPad::GetSteeringUpDown, PATCH_JUMP); + InjectHook(0x493070, &CPad::GetCarGunUpDown, PATCH_JUMP); + InjectHook(0x4930C0, &CPad::GetCarGunLeftRight, PATCH_JUMP); + InjectHook(0x493110, &CPad::GetPedWalkLeftRight, PATCH_JUMP); + InjectHook(0x493190, &CPad::GetPedWalkUpDown, PATCH_JUMP); + InjectHook(0x493210, &CPad::GetAnalogueUpDown, PATCH_JUMP); + InjectHook(0x493290, &CPad::GetLookLeft, PATCH_JUMP); + InjectHook(0x4932C0, &CPad::GetLookRight, PATCH_JUMP); + InjectHook(0x4932F0, &CPad::GetLookBehindForCar, PATCH_JUMP); + InjectHook(0x493320, &CPad::GetLookBehindForPed, PATCH_JUMP); + InjectHook(0x493350, &CPad::GetHorn, PATCH_JUMP); + InjectHook(0x4933F0, &CPad::HornJustDown, PATCH_JUMP); + InjectHook(0x493490, &CPad::GetCarGunFired, PATCH_JUMP); + InjectHook(0x4934F0, &CPad::CarGunJustDown, PATCH_JUMP); + InjectHook(0x493560, &CPad::GetHandBrake, PATCH_JUMP); + InjectHook(0x4935A0, &CPad::GetBrake, PATCH_JUMP); + InjectHook(0x4935F0, &CPad::GetExitVehicle, PATCH_JUMP); + InjectHook(0x493650, &CPad::ExitVehicleJustDown, PATCH_JUMP); + InjectHook(0x4936C0, &CPad::GetWeapon, PATCH_JUMP); + InjectHook(0x493700, &CPad::WeaponJustDown, PATCH_JUMP); + InjectHook(0x493780, &CPad::GetAccelerate, PATCH_JUMP); + InjectHook(0x4937D0, &CPad::CycleCameraModeUpJustDown, PATCH_JUMP); + InjectHook(0x493830, &CPad::CycleCameraModeDownJustDown, PATCH_JUMP); + InjectHook(0x493870, &CPad::ChangeStationJustDown, PATCH_JUMP); + InjectHook(0x493910, &CPad::CycleWeaponLeftJustDown, PATCH_JUMP); + InjectHook(0x493940, &CPad::CycleWeaponRightJustDown, PATCH_JUMP); + InjectHook(0x493970, &CPad::GetTarget, PATCH_JUMP); + InjectHook(0x4939D0, &CPad::TargetJustDown, PATCH_JUMP); + InjectHook(0x493A40, &CPad::JumpJustDown, PATCH_JUMP); + InjectHook(0x493A70, &CPad::GetSprint, PATCH_JUMP); + InjectHook(0x493AE0, &CPad::ShiftTargetLeftJustDown, PATCH_JUMP); + InjectHook(0x493B10, &CPad::ShiftTargetRightJustDown, PATCH_JUMP); + InjectHook(0x493B40, &CPad::GetAnaloguePadUp, PATCH_JUMP); + InjectHook(0x493BA0, &CPad::GetAnaloguePadDown, PATCH_JUMP); + InjectHook(0x493C00, &CPad::GetAnaloguePadLeft, PATCH_JUMP); + InjectHook(0x493C60, &CPad::GetAnaloguePadRight, PATCH_JUMP); + InjectHook(0x493CC0, &CPad::GetAnaloguePadLeftJustUp, PATCH_JUMP); + InjectHook(0x493D20, &CPad::GetAnaloguePadRightJustUp, PATCH_JUMP); + InjectHook(0x493D80, &CPad::ForceCameraBehindPlayer, PATCH_JUMP); + InjectHook(0x493E00, &CPad::SniperZoomIn, PATCH_JUMP); + InjectHook(0x493E70, &CPad::SniperZoomOut, PATCH_JUMP); + InjectHook(0x493EE0, &CPad::SniperModeLookLeftRight, PATCH_JUMP); + InjectHook(0x493F30, &CPad::SniperModeLookUpDown, PATCH_JUMP); + InjectHook(0x493F80, &CPad::LookAroundLeftRight, PATCH_JUMP); + InjectHook(0x494130, &CPad::LookAroundUpDown, PATCH_JUMP); + InjectHook(0x494290, &CPad::ResetAverageWeapon, PATCH_JUMP); + InjectHook(0x4942B0, CPad::PrintErrorMessage, PATCH_JUMP); + InjectHook(0x494420, LittleTest, PATCH_JUMP); + InjectHook(0x494450, CPad::ResetCheats, PATCH_JUMP); + InjectHook(0x4944B0, CPad::EditString, PATCH_JUMP); + InjectHook(0x494690, CPad::EditCodesForControls, PATCH_JUMP); + + //InjectHook(0x494E50, `global constructor keyed to'Pad.cpp, PATCH_JUMP); + //InjectHook(0x494EB0, sub_494EB0, PATCH_JUMP); + //InjectHook(0x494ED0, &CPad::~CPad, PATCH_JUMP); + //InjectHook(0x494EE0, &CPad::CPad, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Pad.h b/src/core/Pad.h new file mode 100644 index 00000000..30cdb8df --- /dev/null +++ b/src/core/Pad.h @@ -0,0 +1,367 @@ +#pragma once + +// same as RW skeleton +/* +enum Key +{ + // ascii... + + KEY_ESC = 128, + + KEY_F1 = 129, + KEY_F2 = 130, + KEY_F3 = 131, + KEY_F4 = 132, + KEY_F5 = 133, + KEY_F6 = 134, + KEY_F7 = 135, + KEY_F8 = 136, + KEY_F9 = 137, + KEY_F10 = 138, + KEY_F11 = 139, + KEY_F12 = 140, + + KEY_INS = 141, + KEY_DEL = 142, + KEY_HOME = 143, + KEY_END = 144, + KEY_PGUP = 145, + KEY_PGDN = 146, + + KEY_UP = 147, + KEY_DOWN = 148, + KEY_LEFT = 149, + KEY_RIGHT = 150, + + // some stuff ommitted + + KEY_BACKSP = 168, + KEY_TAB = 169, + KEY_CAPSLK = 170, + KEY_ENTER = 171, + KEY_LSHIFT = 172, + KEY_RSHIFT = 173, + KEY_LCTRL = 174, + KEY_RCTRL = 175, + KEY_LALT = 176, + KEY_RALT = 177, + + KEY_NULL, // unused + KEY_NUMKEYS +}; +*/ + + +class CControllerState +{ +public: + int16 LeftStickX, LeftStickY; + int16 RightStickX, RightStickY; + int16 LeftShoulder1, LeftShoulder2; + int16 RightShoulder1, RightShoulder2; + int16 DPadUp, DPadDown, DPadLeft, DPadRight; + int16 Start, Select; + int16 Square, Triangle, Cross, Circle; + int16 LeftShock, RightShock; + int16 NetworkTalk; + float GetLeftStickX(void) { return LeftStickX/32767.0f; }; + float GetLeftStickY(void) { return LeftStickY/32767.0f; }; + float GetRightStickX(void) { return RightStickX/32767.0f; }; + float GetRightStickY(void) { return RightStickY/32767.0f; }; + + void Clear(void); +}; +VALIDATE_SIZE(CControllerState, 0x2A); + +class CMouseControllerState +{ +public: + //uint32 btns; // bit 0-2 button 1-3 + + bool LMB; + bool RMB; + bool MMB; + bool WHEELUP; + bool WHEELDN; + bool MXB1; + bool MXB2; + char _pad0; + + float x, y; + + CMouseControllerState(); + void Clear(); +}; + +VALIDATE_SIZE(CMouseControllerState, 0x10); + +class CMousePointerStateHelper +{ +public: + bool bInvertHorizontally; + bool bInvertVertically; + + CMouseControllerState GetMouseSetUp(); +}; + +VALIDATE_SIZE(CMousePointerStateHelper, 0x2); + +extern CMousePointerStateHelper &MousePointerStateHelper; + + +class CKeyboardState +{ +public: + int16 F[12]; + int16 VK_KEYS[256]; + int16 ESC; + int16 INS; + int16 DEL; + int16 HOME; + int16 END; + int16 PGUP; + int16 PGDN; + int16 UP; + int16 DOWN; + int16 LEFT; + int16 RIGHT; + int16 SCROLLLOCK; + int16 PAUSE; + int16 NUMLOCK; + int16 DIV; + int16 MUL; + int16 SUB; + int16 ADD; + int16 ENTER; + int16 DECIMAL; + int16 NUM1; + int16 NUM2; + int16 NUM3; + int16 NUM4; + int16 NUM5; + int16 NUM6; + int16 NUM7; + int16 NUM8; + int16 NUM9; + int16 NUM0; + int16 BACKSP; + int16 TAB; + int16 CAPSLOCK; + int16 EXTENTER; + int16 LSHIFT; + int16 RSHIFT; + int16 SHIFT; + int16 LCTRL; + int16 RCTRL; + int16 LALT; + int16 RALT; + int16 LWIN; + int16 RWIN; + int16 APPS; + + void Clear(); +}; + +VALIDATE_SIZE(CKeyboardState, 0x270); + +enum +{ + // taken from miss2 + PAD1 = 0, + PAD2, + + MAX_PADS +}; + +class CPad +{ +public: + CControllerState NewState; + CControllerState OldState; + CControllerState PCTempKeyState; + CControllerState PCTempJoyState; + CControllerState PCTempMouseState; + // straight out of my IDB + int16 Phase; + int16 Mode; + int16 ShakeDur; + uint8 ShakeFreq; + int8 bHornHistory[5]; + uint8 iCurrHornHistory; + bool DisablePlayerControls; + int8 bApplyBrakes; + char _unk[12]; //int32 unk[3]; + char _pad0[3]; + int32 LastTimeTouched; + int32 AverageWeapon; + int32 AverageEntries; + + CPad() { } + ~CPad() { } + + static bool &bDisplayNoControllerMessage; + static bool &bObsoleteControllerMessage; + static bool &m_bMapPadOneToPadTwo; + + static CKeyboardState &OldKeyState; + static CKeyboardState &NewKeyState; + static CKeyboardState &TempKeyState; + static char KeyBoardCheatString[18]; + static CMouseControllerState &OldMouseControllerState; + static CMouseControllerState &NewMouseControllerState; + static CMouseControllerState &PCTempMouseControllerState; + + + + + void Clear(bool bResetPlayerControls); + void ClearMouseHistory(); + void UpdateMouse(); + CControllerState ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2); + void StartShake(int16 nDur, uint8 nFreq); + void StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fz); + void StartShake_Train(float fX, float fY); + void AddToPCCheatString(char c); + + static void UpdatePads(void); + void ProcessPCSpecificStuff(void); + void Update(int16 unk); + + static void DoCheats(void); + void DoCheats(int16 unk); + + static void StopPadsShaking(void); + void StopShaking(int16 unk); + + static CPad *GetPad(int32 pad); + + int16 GetSteeringLeftRight(void); + int16 GetSteeringUpDown(void); + int16 GetCarGunUpDown(void); + int16 GetCarGunLeftRight(void); + int16 GetPedWalkLeftRight(void); + int16 GetPedWalkUpDown(void); + int16 GetAnalogueUpDown(void); + bool GetLookLeft(void); + bool GetLookRight(void); + bool GetLookBehindForCar(void); + bool GetLookBehindForPed(void); + bool GetHorn(void); + bool HornJustDown(void); + bool GetCarGunFired(void); + bool CarGunJustDown(void); + int16 GetHandBrake(void); + int16 GetBrake(void); + bool GetExitVehicle(void); + bool ExitVehicleJustDown(void); + int32 GetWeapon(void); + bool WeaponJustDown(void); + int16 GetAccelerate(void); + bool CycleCameraModeUpJustDown(void); + bool CycleCameraModeDownJustDown(void); + bool ChangeStationJustDown(void); + bool CycleWeaponLeftJustDown(void); + bool CycleWeaponRightJustDown(void); + bool GetTarget(void); + bool TargetJustDown(void); + bool JumpJustDown(void); + bool GetSprint(void); + bool ShiftTargetLeftJustDown(void); + bool ShiftTargetRightJustDown(void); + bool GetAnaloguePadUp(void); + bool GetAnaloguePadDown(void); + bool GetAnaloguePadLeft(void); + bool GetAnaloguePadRight(void); + bool GetAnaloguePadLeftJustUp(void); + bool GetAnaloguePadRightJustUp(void); + bool ForceCameraBehindPlayer(void); + bool SniperZoomIn(void); + bool SniperZoomOut(void); + int16 SniperModeLookLeftRight(void); + int16 SniperModeLookUpDown(void); + int16 LookAroundLeftRight(void); + int16 LookAroundUpDown(void); + void ResetAverageWeapon(void); + static void PrintErrorMessage(void); + static void ResetCheats(void); + static char *EditString(char *pStr, int32 nSize); + static int32 *EditCodesForControls(int32 *pRsKeys, int32 nSize); + + // mouse + bool GetLeftMouseJustDown() { return !!(NewMouseControllerState.LMB && !OldMouseControllerState.LMB); } + + // keyboard + + bool GetCharJustDown(int32 c) { return !!(NewKeyState.VK_KEYS[c] && !OldKeyState.VK_KEYS[c]); } + bool GetFJustDown(int32 n) { return !!(NewKeyState.F[n] && !OldKeyState.F[n]); } + bool GetEscapeJustDown() { return !!(NewKeyState.ESC && !OldKeyState.ESC); } + bool GetInsertJustDown() { return !!(NewKeyState.INS && !OldKeyState.INS); } + bool GetDeleteJustDown() { return !!(NewKeyState.DEL && !OldKeyState.DEL); } + bool GetHomeJustDown() { return !!(NewKeyState.HOME && !OldKeyState.HOME); } + bool GetEndJustDown() { return !!(NewKeyState.END && !OldKeyState.END); } + bool GetPageUpJustDown() { return !!(NewKeyState.PGUP && !OldKeyState.PGUP); } + bool GetPageDownJustDown() { return !!(NewKeyState.PGDN && !OldKeyState.PGDN); } + bool GetUpJustDown() { return !!(NewKeyState.UP && !OldKeyState.UP); } + bool GetDownJustDown() { return !!(NewKeyState.DOWN && !OldKeyState.DOWN); } + bool GetLeftJustDown() { return !!(NewKeyState.LEFT && !OldKeyState.LEFT); } + bool GetRightJustDown() { return !!(NewKeyState.RIGHT && !OldKeyState.RIGHT); } + bool GetScrollLockJustDown() { return !!(NewKeyState.SCROLLLOCK && !OldKeyState.SCROLLLOCK); } + bool GetPauseJustDown() { return !!(NewKeyState.PAUSE && !OldKeyState.PAUSE); } + bool GetNumLockJustDown() { return !!(NewKeyState.NUMLOCK && !OldKeyState.NUMLOCK); } + bool GetDivideJustDown() { return !!(NewKeyState.DIV && !OldKeyState.DIV); } + bool GetTimesJustDown() { return !!(NewKeyState.MUL && !OldKeyState.MUL); } + bool GetMinusJustDown() { return !!(NewKeyState.SUB && !OldKeyState.SUB); } + bool GetPlusJustDown() { return !!(NewKeyState.ADD && !OldKeyState.ADD); } + bool GetPadEnterJustDown() { return !!(NewKeyState.ENTER && !OldKeyState.ENTER); } // GetEnterJustDown + bool GetPadDelJustDown() { return !!(NewKeyState.DECIMAL && !OldKeyState.DECIMAL); } + bool GetPad1JustDown() { return !!(NewKeyState.NUM1 && !OldKeyState.NUM1); } + bool GetPad2JustDown() { return !!(NewKeyState.NUM2 && !OldKeyState.NUM2); } + bool GetPad3JustDown() { return !!(NewKeyState.NUM3 && !OldKeyState.NUM3); } + bool GetPad4JustDown() { return !!(NewKeyState.NUM4 && !OldKeyState.NUM4); } + bool GetPad5JustDown() { return !!(NewKeyState.NUM5 && !OldKeyState.NUM5); } + bool GetPad6JustDown() { return !!(NewKeyState.NUM6 && !OldKeyState.NUM6); } + bool GetPad7JustDown() { return !!(NewKeyState.NUM7 && !OldKeyState.NUM7); } + bool GetPad8JustDown() { return !!(NewKeyState.NUM8 && !OldKeyState.NUM8); } + bool GetPad9JustDown() { return !!(NewKeyState.NUM9 && !OldKeyState.NUM9); } + bool GetPad0JustDown() { return !!(NewKeyState.NUM0 && !OldKeyState.NUM0); } + bool GetBackspaceJustDown() { return !!(NewKeyState.BACKSP && !OldKeyState.BACKSP); } + bool GetTabJustDown() { return !!(NewKeyState.TAB && !OldKeyState.TAB); } + bool GetCapsLockJustDown() { return !!(NewKeyState.CAPSLOCK && !OldKeyState.CAPSLOCK); } + bool GetEnterJustDown() { return !!(NewKeyState.EXTENTER && !OldKeyState.EXTENTER); } + bool GetLeftShiftJustDown() { return !!(NewKeyState.LSHIFT && !OldKeyState.LSHIFT); } + bool GetShiftJustDown() { return !!(NewKeyState.SHIFT && !OldKeyState.SHIFT); } + bool GetRightShiftJustDown() { return !!(NewKeyState.RSHIFT && !OldKeyState.RSHIFT); } + bool GetLeftCtrlJustDown() { return !!(NewKeyState.LCTRL && !OldKeyState.LCTRL); } + bool GetRightCtrlJustDown() { return !!(NewKeyState.RCTRL && !OldKeyState.RCTRL); } + bool GetLeftAltJustDown() { return !!(NewKeyState.LALT && !OldKeyState.LALT); } + bool GetRightAltJustDown() { return !!(NewKeyState.RALT && !OldKeyState.RALT); } + bool GetLeftWinJustDown() { return !!(NewKeyState.LWIN && !OldKeyState.LWIN); } + bool GetRightWinJustDown() { return !!(NewKeyState.RWIN && !OldKeyState.RWIN); } + bool GetAppsJustDown() { return !!(NewKeyState.APPS && !OldKeyState.APPS); } + + // pad + + bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); } + bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); } + bool GetCrossJustDown() { return !!(NewState.Cross && !OldState.Cross); } + bool GetSquareJustDown() { return !!(NewState.Square && !OldState.Square); } + bool GetDPadUpJustDown() { return !!(NewState.DPadUp && !OldState.DPadUp); } + bool GetDPadDownJustDown() { return !!(NewState.DPadDown && !OldState.DPadDown); } + bool GetDPadLeftJustDown() { return !!(NewState.DPadLeft && !OldState.DPadLeft); } + bool GetDPadRightJustDown() { return !!(NewState.DPadRight && !OldState.DPadRight); } + bool GetLeftShoulder1JustDown() { return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); } + bool GetLeftShoulder2JustDown() { return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); } + bool GetRightShoulder1JustDown() { return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); } + bool GetRightShoulder2JustDown() { return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); } + + int32 GetLeftShoulder1(void) { return NewState.LeftShoulder1; } + int32 GetLeftShoulder2(void) { return NewState.LeftShoulder2; } + int32 GetRightShoulder1(void) { return NewState.RightShoulder1; } + int32 GetRightShoulder2(void) { return NewState.RightShoulder2; } +}; +VALIDATE_SIZE(CPad, 0xFC); + +#define IsButtonJustDown(pad, btn) \ + (!(pad)->OldState.btn && (pad)->NewState.btn) + +void LittleTest(void); diff --git a/src/core/Placeable.cpp b/src/core/Placeable.cpp new file mode 100644 index 00000000..b4b2a37b --- /dev/null +++ b/src/core/Placeable.cpp @@ -0,0 +1,72 @@ +#include "common.h" +#include "Placeable.h" +#include "patcher.h" + +CPlaceable::CPlaceable(void) +{ + m_matrix.SetScale(1.0f); +} + +CPlaceable::~CPlaceable(void) { } + +void +CPlaceable::SetHeading(float angle) +{ + CVector pos = GetPosition(); + m_matrix.SetRotateZ(angle); + GetPosition() += pos; +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float x2, float y2) +{ + float tmp; + + if(x1 > x2){ + tmp = x1; + x1 = x2; + x2 = tmp; + } + if(y1 > y2){ + tmp = y1; + y1 = y2; + y2 = tmp; + } + + return x1 <= GetPosition().x && GetPosition().x <= x2 && + y1 <= GetPosition().y && GetPosition().y <= y2; +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + float tmp; + + if(x1 > x2){ + tmp = x1; + x1 = x2; + x2 = tmp; + } + if(y1 > y2){ + tmp = y1; + y1 = y2; + y2 = tmp; + } + if(z1 > z2){ + tmp = z1; + z1 = z2; + z2 = tmp; + } + + return x1 <= GetPosition().x && GetPosition().x <= x2 && + y1 <= GetPosition().y && GetPosition().y <= y2 && + z1 <= GetPosition().z && GetPosition().z <= z2; +} + +STARTPATCHES + InjectHook(0x49F9A0, &CPlaceable::ctor, PATCH_JUMP); + InjectHook(0x49F9E0, &CPlaceable::dtor, PATCH_JUMP); + InjectHook(0x49FA00, &CPlaceable::SetHeading, PATCH_JUMP); + InjectHook(0x49FA50, (bool (CPlaceable::*)(float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP); + InjectHook(0x49FAF0, (bool (CPlaceable::*)(float, float, float, float, float, float))&CPlaceable::IsWithinArea, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Placeable.h b/src/core/Placeable.h new file mode 100644 index 00000000..868ca9e7 --- /dev/null +++ b/src/core/Placeable.h @@ -0,0 +1,26 @@ +#pragma once + +class CPlaceable +{ +public: + // disable allocation + static void *operator new(size_t) = delete; + + CMatrix m_matrix; + + CPlaceable(void); + virtual ~CPlaceable(void); + CVector &GetPosition(void) { return *m_matrix.GetPosition(); } + CVector &GetRight(void) { return *m_matrix.GetRight(); } + CVector &GetForward(void) { return *m_matrix.GetForward(); } + CVector &GetUp(void) { return *m_matrix.GetUp(); } + CMatrix &GetMatrix(void) { return m_matrix; } + void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); } + void SetHeading(float angle); + bool IsWithinArea(float x1, float y1, float x2, float y2); + bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2); + + CPlaceable *ctor(void) { return ::new (this) CPlaceable(); } + void dtor(void) { this->CPlaceable::~CPlaceable(); } +}; +static_assert(sizeof(CPlaceable) == 0x4C, "CPlaceable: error"); diff --git a/src/core/PlayerInfo.cpp b/src/core/PlayerInfo.cpp new file mode 100644 index 00000000..59efe2ae --- /dev/null +++ b/src/core/PlayerInfo.cpp @@ -0,0 +1,5 @@ +#include "common.h" +#include "patcher.h" +#include "PlayerInfo.h" + +WRAPPER void CPlayerInfo::MakePlayerSafe(bool) { EAXJMP(0x4A1400); }
\ No newline at end of file diff --git a/src/core/PlayerInfo.h b/src/core/PlayerInfo.h new file mode 100644 index 00000000..e2b42fe7 --- /dev/null +++ b/src/core/PlayerInfo.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Collision.h" + +enum eWastedBustedState +{ + WBSTATE_PLAYING, + WBSTATE_WASTED, + WBSTATE_BUSTED, + WBSTATE_FAILED_CRITICAL_MISSION, +}; + +class CVehicle; +class CPlayerPed; +class CCivilianPed; + +class CPlayerInfo +{ +public: + CPlayerPed *m_pPed; + CVehicle *m_pRemoteVehicle; + CColModel m_ColModel; + CVehicle *m_pVehicleEx; + char m_aPlayerName[70]; + int32 m_nMoney; + int32 m_nVisibleMoney; + int32 m_nCollectedPackages; + int32 m_nTotalPackages; + int32 field_188; + int32 m_nSwitchTaxiTime; + bool m_bSwitchTaxi; + int8 field_197; + int8 field_198; + int8 field_199; + int32 m_nNextSexFrequencyUpdateTime; + int32 m_nNextSexMoneyUpdateTime; + int32 m_nSexFrequency; + CCivilianPed *m_pHooker; + int8 m_WBState; // eWastedBustedState + int8 field_217; + int8 field_218; + int8 field_219; + int32 m_nWBTime; + bool m_bInRemoteMode; + int8 field_225; + int8 field_226; + int8 field_227; + int32 m_nTimeLostRemoteCar; + int32 m_nTimeLastHealthLoss; + int32 m_nTimeLastArmourLoss; + int32 field_240; + int32 m_nUpsideDownCounter; + int32 field_248; + int16 m_nTrafficMultiplier; + int8 field_254; + int8 field_255; + float m_fRoadDensity; + int32 m_nPreviousTimeRewardedForExplosion; + int32 m_nExplosionsSinceLastReward; + int32 field_268; + int32 field_272; + bool m_bInfiniteSprint; + bool m_bFastReload; + bool m_bGetOutOfJailFree; + bool m_bGetOutOfHospitalFree; + uint8 m_aSkinName[32]; + RwTexture *m_pSkinTexture; + + void MakePlayerSafe(bool); +}; + +static_assert(sizeof(CPlayerInfo) == 0x13C, "CPlayerInfo: error"); diff --git a/src/core/PlayerSkin.cpp b/src/core/PlayerSkin.cpp new file mode 100644 index 00000000..1c9ca2c6 --- /dev/null +++ b/src/core/PlayerSkin.cpp @@ -0,0 +1,5 @@ +#include "common.h" +#include "patcher.h" +#include "PlayerSkin.h" + +WRAPPER void CPlayerSkin::BeginFrontEndSkinEdit() { EAXJMP(0x59BC70); } diff --git a/src/core/PlayerSkin.h b/src/core/PlayerSkin.h new file mode 100644 index 00000000..61e09cdf --- /dev/null +++ b/src/core/PlayerSkin.h @@ -0,0 +1,7 @@ +#pragma once + +class CPlayerSkin +{ +public: + static void BeginFrontEndSkinEdit(); +};
\ No newline at end of file diff --git a/src/core/Pools.cpp b/src/core/Pools.cpp new file mode 100644 index 00000000..f7f93292 --- /dev/null +++ b/src/core/Pools.cpp @@ -0,0 +1,25 @@ +#include "common.h" +#include "Pools.h" + +CCPtrNodePool *&CPools::ms_pPtrNodePool = *(CCPtrNodePool**)0x943044; +CEntryInfoNodePool *&CPools::ms_pEntryInfoNodePool = *(CEntryInfoNodePool**)0x941448; +CPedPool *&CPools::ms_pPedPool = *(CPedPool**)0x8F2C60; +CVehiclePool *&CPools::ms_pVehiclePool = *(CVehiclePool**)0x9430DC; +CBuildingPool *&CPools::ms_pBuildingPool = *(CBuildingPool**)0x8F2C04; +CTreadablePool *&CPools::ms_pTreadablePool = *(CTreadablePool**)0x8F2568; +CObjectPool *&CPools::ms_pObjectPool = *(CObjectPool**)0x880E28; +CDummyPool *&CPools::ms_pDummyPool = *(CDummyPool**)0x8F2C18; + +void +CPools::Initialise(void) +{ + // TODO: unused right now + ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES); + ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS); + ms_pPedPool = new CPedPool(NUMPEDS); + ms_pVehiclePool = new CVehiclePool(NUMVEHICLES); + ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS); + ms_pTreadablePool = new CTreadablePool(NUMTREADABLES); + ms_pObjectPool = new CObjectPool(NUMOBJECTS); + ms_pDummyPool = new CDummyPool(NUMDUMMIES); +} diff --git a/src/core/Pools.h b/src/core/Pools.h new file mode 100644 index 00000000..3496064c --- /dev/null +++ b/src/core/Pools.h @@ -0,0 +1,43 @@ +#pragma once + +#include "templates.h" +#include "Lists.h" +#include "Treadable.h" +#include "Object.h" +#include "CutsceneHead.h" +#include "PlayerPed.h" +#include "Automobile.h" +#include "DummyPed.h" + +typedef CPool<CPtrNode> CCPtrNodePool; +typedef CPool<CEntryInfoNode> CEntryInfoNodePool; +typedef CPool<CPed,CPlayerPed> CPedPool; +typedef CPool<CVehicle,CAutomobile> CVehiclePool; +typedef CPool<CBuilding> CBuildingPool; +typedef CPool<CTreadable> CTreadablePool; +typedef CPool<CObject, CCutsceneHead> CObjectPool; +typedef CPool<CDummy, CDummyPed> CDummyPool; + +class CPools +{ + static CCPtrNodePool *&ms_pPtrNodePool; + static CEntryInfoNodePool *&ms_pEntryInfoNodePool; + static CPedPool *&ms_pPedPool; + static CVehiclePool *&ms_pVehiclePool; + static CBuildingPool *&ms_pBuildingPool; + static CTreadablePool *&ms_pTreadablePool; + static CObjectPool *&ms_pObjectPool; + static CDummyPool *&ms_pDummyPool; + // ms_pAudioScriptObjectPool +public: + static CCPtrNodePool *GetPtrNodePool(void) { return ms_pPtrNodePool; } + static CEntryInfoNodePool *GetEntryInfoNodePool(void) { return ms_pEntryInfoNodePool; } + static CPedPool *GetPedPool(void) { return ms_pPedPool; } + static CVehiclePool *GetVehiclePool(void) { return ms_pVehiclePool; } + static CBuildingPool *GetBuildingPool(void) { return ms_pBuildingPool; } + static CTreadablePool *GetTreadablePool(void) { return ms_pTreadablePool; } + static CObjectPool *GetObjectPool(void) { return ms_pObjectPool; } + static CDummyPool *GetDummyPool(void) { return ms_pDummyPool; } + + static void Initialise(void); +}; diff --git a/src/core/Radar.cpp b/src/core/Radar.cpp new file mode 100644 index 00000000..a071b96b --- /dev/null +++ b/src/core/Radar.cpp @@ -0,0 +1,1117 @@ +#include "config.h" +#include "common.h" +#include "patcher.h" +#include "RwHelper.h" +#include "Radar.h" +#include "Camera.h" +#include "Hud.h" +#include "World.h" +#include "Frontend.h" +#include "General.h" +#include "Vehicle.h" +#include "Pools.h" +#include "Script.h" +#include "TxdStore.h" +#include "World.h" +#include "Streaming.h" + +float &CRadar::m_RadarRange = *(float*)0x8E281C; +CBlip *CRadar::ms_RadarTrace = (CBlip*)0x6ED5E0; + +CVector2D &vec2DRadarOrigin = *(CVector2D*)0x6299B8; +int *gRadarTxdIds = (int*)0x6299C0; + +CSprite2d *CRadar::AsukaSprite = (CSprite2d*)0x8F1A40; +CSprite2d *CRadar::BombSprite = (CSprite2d*)0x8F5FB4; +CSprite2d *CRadar::CatSprite = (CSprite2d*)0x885B24; +CSprite2d *CRadar::CentreSprite = (CSprite2d*)0x8F6268; +CSprite2d *CRadar::CopcarSprite = (CSprite2d*)0x8F1A2C; +CSprite2d *CRadar::DonSprite = (CSprite2d*)0x8F2BE0; +CSprite2d *CRadar::EightSprite = (CSprite2d*)0x8F2BCC; +CSprite2d *CRadar::ElSprite = (CSprite2d*)0x8F1B80; +CSprite2d *CRadar::IceSprite = (CSprite2d*)0x9415FC; +CSprite2d *CRadar::JoeySprite = (CSprite2d*)0x8F2C00; +CSprite2d *CRadar::KenjiSprite = (CSprite2d*)0x8F2C68; +CSprite2d *CRadar::LizSprite = (CSprite2d*)0x8F5830; +CSprite2d *CRadar::LuigiSprite = (CSprite2d*)0x8F1A3C; +CSprite2d *CRadar::NorthSprite = (CSprite2d*)0x8F6274; +CSprite2d *CRadar::RaySprite = (CSprite2d*)0x8E2A7C; +CSprite2d *CRadar::SalSprite = (CSprite2d*)0x8F29EC; +CSprite2d *CRadar::SaveSprite = (CSprite2d*)0x8F5F74; +CSprite2d *CRadar::SpraySprite = (CSprite2d*)0x94307C; +CSprite2d *CRadar::TonySprite = (CSprite2d*)0x885B58; +CSprite2d *CRadar::WeaponSprite = (CSprite2d*)0x941534; + +CSprite2d *CRadar::RadarSprites[RADAR_SPRITE_COUNT] = { + nil, + AsukaSprite, + BombSprite, + CatSprite, + CentreSprite, + CopcarSprite, + DonSprite, + EightSprite, + ElSprite, + IceSprite, + JoeySprite, + KenjiSprite, + LizSprite, + LuigiSprite, + NorthSprite, + RaySprite, + SalSprite, + SaveSprite, + SpraySprite, + TonySprite, + WeaponSprite +}; + +#define RADAR_NUM_TILES (8) +#define RADAR_TILE_SIZE (WORLD_SIZE_X / RADAR_NUM_TILES) +static_assert(RADAR_TILE_SIZE == (WORLD_SIZE_Y / RADAR_NUM_TILES), "CRadar: not a square"); + +#define RADAR_MIN_RANGE (120.0f) +#define RADAR_MAX_RANGE (350.0f) +#define RADAR_MIN_SPEED (0.3f) +#define RADAR_MAX_SPEED (0.9f) + +#if 0 +WRAPPER void CRadar::CalculateBlipAlpha(float) { EAXJMP(0x4A4F90); } +#else +int CRadar::CalculateBlipAlpha(float dist) +{ + if (dist <= 1.0f) + return 255; + + if (dist <= 5.0f) + return (((1.0f - ((dist * 0.25f) - 0.25f)) * 255.0f) + (((dist * 0.25f) - 0.25f) * 128.0f)); + + return 128; +} +#endif + +#if 1 +WRAPPER void CRadar::ChangeBlipBrightness(int32, int32) { EAXJMP(0x4A57A0); } +#else +void CRadar::ChangeBlipBrightness(int32 i, int32 bright) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::ChangeBlipColour(int32) { EAXJMP(0x4A5770); } +#else +void CRadar::ChangeBlipColour(int32 i) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::ChangeBlipDisplay(int32, int16) { EAXJMP(0x4A5810); } +#else +void CRadar::ChangeBlipDisplay(int32 i, int16 flag) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::ChangeBlipScale(int32, int16) { EAXJMP(0x4A57E0); } +#else +void CRadar::ChangeBlipScale(int32 i, int16 scale) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::ClearBlip(int32) { EAXJMP(0x4A5720); } +#else +void CRadar::ClearBlip(int32 i) +{ + +} +#endif + +#if 0 +WRAPPER void CRadar::ClearBlipForEntity(int16, int32) { EAXJMP(0x4A56C0); } +#else +void CRadar::ClearBlipForEntity(int16 type, int32 id) +{ + for (int i = 0; i < NUMRADARBLIPS; i++) { + if (type == ms_RadarTrace[i].m_eBlipType && id == ms_RadarTrace[i].m_nEntityHandle) { + SetRadarMarkerState(i, 0); + ms_RadarTrace[i].m_bInUse = 0; + ms_RadarTrace[i].m_eBlipType = 0; + ms_RadarTrace[i].m_eBlipDisplay = 0; + ms_RadarTrace[i].m_IconID = 0; + } + }; +} +#endif + +#if 0 +WRAPPER int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *in) { EAXJMP(0x4A64A0); } +#else +// Why not a proper clipping algorithm? +int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect) +{ + CVector2D corners[4] = { + { 1.0f, -1.0f }, // top right + { 1.0f, 1.0f }, // bottom right + { -1.0f, 1.0f }, // bottom left + { -1.0f, -1.0f }, // top left + }; + CVector2D tmp; + int i, j, n; + int laste, e, e1, e2;; + bool inside[4]; + + for (i = 0; i < 4; i++) + inside[i] = IsPointInsideRadar(rect[i]); + + laste = -1; + n = 0; + for (i = 0; i < 4; i++) + if (inside[i]) { + // point is inside, just add + poly[n++] = rect[i]; + } + else { + // point is outside but line to this point might be clipped + e1 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 4 - 1) % 4]); + if (e1 != -1) { + laste = e1; + n++; + } + // and line from this point might be clipped as well + e2 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 1) % 4]); + if (e2 != -1) { + if (e1 == -1) { + // if other line wasn't clipped, i.e. it was complete outside, + // we may have to insert another vertex if last clipped line + // was on a different edge + + // find the last intersection if we haven't seen it yet + if (laste == -1) + for (j = 3; j >= i; j--) { + // game uses an if here for j == 0 + e = LineRadarBoxCollision(tmp, rect[j], rect[(j + 4 - 1) % 4]); + if (e != -1) { + laste = e; + break; + } + } + assert(laste != -1); + + // insert corners that were skipped + tmp = poly[n]; + for (e = laste; e != e2; e = (e + 1) % 4) + poly[n++] = corners[e]; + poly[n] = tmp; + } + n++; + } + } + + if (n == 0) { + // If no points, either the rectangle is completely outside or completely surrounds the radar + // no idea what's going on here... + float m = (rect[0].y - rect[1].y) / (rect[0].x - rect[1].x); + if ((m*rect[3].x - rect[3].y) * (m*rect[0].x - rect[0].y) < 0.0f) { + m = (rect[0].y - rect[3].y) / (rect[0].x - rect[3].x); + if ((m*rect[1].x - rect[1].y) * (m*rect[0].x - rect[0].y) < 0.0f) { + poly[0] = corners[0]; + poly[1] = corners[1]; + poly[2] = corners[2]; + poly[3] = corners[3]; + n = 4; + } + } + } + + return n; +} +#endif + +bool CRadar::DisplayThisBlip(int32 counter) +{ + switch (ms_RadarTrace[counter].m_IconID) { + case RADAR_SPRITE_BOMB: + case RADAR_SPRITE_SPRAY: + case RADAR_SPRITE_WEAPON: + return true; + default: + return false; + } +} + +#if 1 +WRAPPER void CRadar::Draw3dMarkers() { EAXJMP(0x4A4C70); } +#else +void CRadar::Draw3dMarkers() +{ + +} +#endif + + +#if 0 +WRAPPER void CRadar::DrawBlips() { EAXJMP(0x4A42F0); } +#else +void CRadar::DrawBlips() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + + CVector2D out; + CVector2D in = CVector2D(0.0f, 0.0f); + TransformRadarPointToScreenSpace(out, in); + + float angle; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1) + angle = PI + FindPlayerHeading(); + else + angle = FindPlayerHeading() - (PI + TheCamera.GetForward().Heading()); + + DrawRotatingRadarSprite(CentreSprite, out.x, out.y, angle, 255); + + CVector2D vec2d; + vec2d.x = vec2DRadarOrigin.x; + vec2d.y = M_SQRT2 * m_RadarRange + vec2DRadarOrigin.y; + TransformRealWorldPointToRadarSpace(in, vec2d); + LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + DrawRadarSprite(RADAR_SPRITE_NORTH, out.x, out.y, 255); + + /* + DrawEntityBlip + */ + for (int i = 0; i < NUMRADARBLIPS; i++) { + if (ms_RadarTrace[i].m_bInUse) { + if (ms_RadarTrace[i].m_eBlipType <= BLIP_OBJECT) { + CEntity *e = nil; + switch (ms_RadarTrace[i].m_eBlipType) { + case BLIP_CAR: + e = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + break; + case BLIP_CHAR: + e = CPools::GetPedPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + break; + case BLIP_OBJECT: + e = CPools::GetObjectPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + break; + }; + + if (e) { + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::DbgFlag) { + ShowRadarMarker(e->GetPosition(), GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim), ms_RadarTrace->m_Radius); + + ms_RadarTrace[i].m_Radius = ms_RadarTrace[i].m_Radius - 0.1f; + if (ms_RadarTrace[i].m_Radius >= 1.0f) + ms_RadarTrace[i].m_Radius = 5.0; + } + } + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + vec2d = e->GetPosition(); + TransformRealWorldPointToRadarSpace(in, vec2d); + float dist = LimitRadarPoint(in); + int a = CalculateBlipAlpha(dist); + TransformRadarPointToScreenSpace(out, in); + + int32 col = GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim); + + if (ms_RadarTrace[i].m_IconID) + DrawRadarSprite(ms_RadarTrace[i].m_IconID, out.x, out.y, a); + else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[i].m_wScale, ((col >> 24)), ((col >> 16) & 0xFF), ((col >> 8)), 255); + } + } + } + + /* + DrawCoordBlip + */ + if (ms_RadarTrace[i].m_eBlipType >= BLIP_COORD) { + if (ms_RadarTrace[i].m_eBlipType != BLIP_CONTACT_POINT || ms_RadarTrace[i].m_eBlipType == BLIP_CONTACT_POINT && DisplayThisBlip(i) || !CTheScripts::IsPlayerOnAMission()) { + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::DbgFlag) { + ShowRadarMarker(ms_RadarTrace[i].m_vecPos, GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim), ms_RadarTrace->m_Radius); + ms_RadarTrace[i].m_Radius = ms_RadarTrace[i].m_Radius - 0.1f; + if (ms_RadarTrace[i].m_Radius >= 1.0f) + ms_RadarTrace[i].m_Radius = 5.0f; + } + } + + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[i].m_vec2DPos); + float dist = LimitRadarPoint(in); + int a = CalculateBlipAlpha(dist); + TransformRadarPointToScreenSpace(out, in); + + int32 col = GetRadarTraceColour(ms_RadarTrace[i].m_nColor, ms_RadarTrace[i].m_bDim); + + if (ms_RadarTrace[i].m_IconID) + DrawRadarSprite(ms_RadarTrace[i].m_IconID, out.x, out.y, a); + else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[i].m_wScale, ((col >> 24)), ((col >> 16) & 0xFF), ((col >> 8)), 255); + } + } + } + }; + } + } +} +#endif + + +#if 0 +WRAPPER void CRadar::DrawMap () { EAXJMP(0x4A4200); } +#else +void CRadar::DrawMap() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { + if (FindPlayerVehicle()) { + float speed = FindPlayerSpeed().Magnitude(); + if (speed < RADAR_MIN_SPEED) + m_RadarRange = RADAR_MIN_RANGE; + else if (speed < RADAR_MAX_SPEED) + m_RadarRange = (speed - RADAR_MIN_SPEED)/(RADAR_MAX_SPEED-RADAR_MIN_SPEED) * (RADAR_MAX_RANGE-RADAR_MIN_RANGE) + RADAR_MIN_RANGE; + else + m_RadarRange = RADAR_MAX_RANGE; + } + else + m_RadarRange = RADAR_MIN_RANGE; + + vec2DRadarOrigin = CVector2D(FindPlayerCentreOfWorld_NoSniperShift()); + DrawRadarMap(); + } +} +#endif + +#if 0 +WRAPPER void CRadar::DrawRadarMap() { EAXJMP(0x4A6C20); } +#else +void CRadar::DrawRadarMap() +{ + // Game calculates an unused CRect here + + DrawRadarMask(); + + // top left ist (0, 0) + int x = floorf((vec2DRadarOrigin.x - WORLD_MIN_X) / RADAR_TILE_SIZE); + int y = ceilf((RADAR_NUM_TILES - 1) - (vec2DRadarOrigin.y - WORLD_MIN_Y) / RADAR_TILE_SIZE); + StreamRadarSections(x, y); + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)FALSE); + + DrawRadarSection(x - 1, y - 1); + DrawRadarSection(x, y - 1); + DrawRadarSection(x + 1, y - 1); + DrawRadarSection(x - 1, y); + DrawRadarSection(x, y); + DrawRadarSection(x + 1, y); + DrawRadarSection(x - 1, y + 1); + DrawRadarSection(x, y + 1); + DrawRadarSection(x + 1, y + 1); +} +#endif + +#if 0 +WRAPPER void CRadar::DrawRadarMask() { EAXJMP(0x4A69C0); } +#else +void CRadar::DrawRadarMask() +{ + CVector2D corners[4] = { + CVector2D(1.0f, -1.0f), + CVector2D(1.0f, 1.0f), + CVector2D(-1.0f, 1.0f), + CVector2D(-1.0, -1.0f) + }; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwD3D8SetRenderState(rwRENDERSTATESTENCILFUNCTION, rwSTENCILFUNCTIONALWAYS); + + CVector2D out[8]; + CVector2D in; + + // Draw the shape we want to mask out from the radar in four segments + for (int i = 0; i < 4; i++) { + // First point is always the corner itself + in.x = corners[i].x; + in.y = corners[i].y; + TransformRadarPointToScreenSpace(out[0], in); + + // Then generate a quarter of the circle + for (int j = 0; j < 7; j++) { + in.x = corners[i].x * cos(j * (PI / 2.0f / 6.0f)); + in.y = corners[i].y * sin(j * (PI / 2.0f / 6.0f)); + TransformRadarPointToScreenSpace(out[j + 1], in); + }; + + CSprite2d::SetMaskVertices(8, (float *)out); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), 8); + }; + + RwD3D8SetRenderState(rwRENDERSTATESTENCILFUNCTION, rwSTENCILFUNCTIONGREATER); +} +#endif + +#if 0 +WRAPPER void CRadar::DrawRadarSection(int32, int32) { EAXJMP(0x4A67E0); } +#else +void CRadar::DrawRadarSection(int32 x, int32 y) +{ + int i; + RwTexDictionary *txd; + CVector2D worldPoly[8]; + CVector2D radarCorners[4]; + CVector2D radarPoly[8]; + CVector2D texCoords[8]; + CVector2D screenPoly[8]; + int numVertices; + RwTexture *texture = nil; + + GetTextureCorners(x, y, worldPoly); + ClipRadarTileCoords(x, y); + + assert(CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])); + txd = CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])->texDict; + if (txd) + texture = GetFirstTexture(txd); + if (texture == nil) + return; + + for (i = 0; i < 4; i++) + TransformRealWorldPointToRadarSpace(radarCorners[i], worldPoly[i]); + + numVertices = ClipRadarPoly(radarPoly, radarCorners); + + // FIX: can return earlier here +// if(numVertices == 0) + if (numVertices < 3) + return; + + for (i = 0; i < numVertices; i++) { + TransformRadarPointToRealWorldSpace(worldPoly[i], radarPoly[i]); + TransformRealWorldToTexCoordSpace(texCoords[i], worldPoly[i], x, y); + TransformRadarPointToScreenSpace(screenPoly[i], radarPoly[i]); + } + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(texture)); + CSprite2d::SetVertices(numVertices, (float*)screenPoly, (float*)texCoords, CRGBA(255, 255, 255, 255)); + // check done above now +// if(numVertices > 2) + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), numVertices); +} +#endif + +#if 0 +WRAPPER void CRadar::DrawRadarSprite(int32 sprite, float x, float y, int32 alpha) { EAXJMP(0x4A5EF0); } +#else +void CRadar::DrawRadarSprite(int32 sprite, float x, float y, int32 alpha) +{ + RadarSprites[sprite]->Draw(CRect(x - SCREEN_SCALE_X(8.0f), y - SCREEN_SCALE_Y(8.0f), x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(8.0f)), CRGBA(255, 255, 255, alpha)); +} +#endif + +#if 0 +WRAPPER void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha) { EAXJMP(0x4A5D10); } +#else +void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha) +{ + CVector curPosn[4]; + CVector oldPosn[4]; + + curPosn[0].x = x - SCREEN_SCALE_X(5.6f); + curPosn[0].y = y + SCREEN_SCALE_Y(5.6f); + + curPosn[1].x = x + SCREEN_SCALE_X(5.6f); + curPosn[1].y = y + SCREEN_SCALE_Y(5.6f); + + curPosn[2].x = x - SCREEN_SCALE_X(5.6f); + curPosn[2].y = y - SCREEN_SCALE_Y(5.6f); + + curPosn[3].x = x + SCREEN_SCALE_X(5.6f); + curPosn[3].y = y - SCREEN_SCALE_Y(5.6f); + + for (uint32 i = 0; i < 4; i++) { + oldPosn[i] = curPosn[i]; + + curPosn[i].x = x + (oldPosn[i].x - x) * cosf(angle) + (oldPosn[i].y - y) * sinf(angle); + curPosn[i].y = y - (oldPosn[i].x - x) * sinf(angle) + (oldPosn[i].y - y) * cosf(angle); + } + + sprite->Draw(curPosn[2].x, curPosn[2].y, curPosn[3].x, curPosn[3].y, curPosn[0].x, curPosn[0].y, curPosn[1].x, curPosn[1].y, CRGBA(255, 255, 255, alpha)); +} +#endif + +#if 1 +WRAPPER int32 CRadar::GetActualBlipArray(int32) { EAXJMP(0x4A41C0); } +#else +int32 CRadar::GetActualBlipArray(int32 i) +{ + return int32(); +} +#endif + +#if 1 +WRAPPER int32 CRadar::GetNewUniqueBlipIndex(int32) { EAXJMP(0x4A4180); } +#else +int32 CRadar::GetNewUniqueBlipIndex(int32 i) +{ + return int32(); +} +#endif + +#if 0 +WRAPPER int32 CRadar::GetRadarTraceColour(int32 color, bool bright) { EAXJMP(0x4A5BB0); } +#else +int32 CRadar::GetRadarTraceColour(int32 color, bool bright) +{ + int32 c; + switch (color) { + case 0: + if (bright) + c = 0x712B49FF; + else + c = 0x7F0000FF; + break; + case 1: + if (bright) + c = 0x5FA06AFF; + else + c = 0x7F00FF; + break; + case 2: + if (bright) + c = 0x80A7F3FF; + else + c = 0x007FFF; + break; + case 3: + if (bright) + c = 0xE1E1E1FF; + else + c = 0x7F7F7FFF; + break; + case 4: + if (bright) + c = 0xFFFF00FF; + else + c = 0x7F7F00FF; + break; + case 5: + if (bright) + c = 0xFF00FFFF; + else + c = 0x7F007FFF; + break; + case 6: + if (bright) + c = 0xFFFFFF; + else + c = 0x7F7FFF; + break; + default: + c = color; + break; + }; + return c; +} +#endif + +#if 1 +WRAPPER void CRadar::Initialise() { EAXJMP(0x4A3EF0); } +#else +void CRadar::Initialise() +{ + +} +#endif + +#if 0 +WRAPPER float CRadar::LimitRadarPoint(CVector2D &point) { EAXJMP(0x4A4F30); } +#else +float CRadar::LimitRadarPoint(CVector2D &point) +{ + float dist, invdist; + + dist = point.Magnitude(); + if (dist > 1.0f) { + invdist = 1.0f / dist; + point.x *= invdist; + point.y *= invdist; + } + return dist; +} +#endif + +#if 1 +WRAPPER void CRadar::LoadAllRadarBlips(int32) { EAXJMP(0x4A6F30); } +#else +void CRadar::LoadAllRadarBlips(int32) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::LoadTextures() { EAXJMP(0x4A4030); } +#else +void CRadar::LoadTextures() +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::RemoveRadarSections() { EAXJMP(0x4A60E0); } +#else +void CRadar::RemoveRadarSections() +{ + +} +#endif + +#if 0 +WRAPPER void CRadar::RemoveMapSection(int32, int32) { EAXJMP(0x00); } +#else +void CRadar::RemoveMapSection(int32 x, int32 y) +{ + if (x >= 0 && x <= 7 && y >= 0 && y <= 7) + CStreaming::RemoveTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y]); +} +#endif + +#if 0 +WRAPPER void CRadar::RequestMapSection(int32, int32) { EAXJMP(0x00); } +#else +void CRadar::RequestMapSection(int32 x, int32 y) +{ + ClipRadarTileCoords(x, y); + CStreaming::RequestTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y], STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY); +} +#endif + +#if 1 +WRAPPER void CRadar::SaveAllRadarBlips(int32) { EAXJMP(0x4A6E30); } +#else +void CRadar::SaveAllRadarBlips(int32) +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::SetBlipSprite(int32, int32) { EAXJMP(0x4A5840); } +#else +void CRadar::SetBlipSprite(int32 i, int32 icon) +{ + +} +#endif + +#if 1 +WRAPPER int CRadar::SetCoordBlip(int32, CVector, int32) { EAXJMP(0x4A5590); } +#else +int CRadar::SetCoordBlip(int32 type, CVector pos, int32 flag) +{ + return 0; +} +#endif + +#if 1 +WRAPPER int CRadar::SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag) { EAXJMP(0x4A5640); } +#else +int CRadar::SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag) +{ + return 0; +} +#endif + +#if 0 +WRAPPER void CRadar::SetRadarMarkerState(int32, int32) { EAXJMP(0x4A5C60); } +#else +void CRadar::SetRadarMarkerState(int32 counter, int32 flag) +{ + CEntity *e; + switch (ms_RadarTrace[counter].m_eBlipType) { + case BLIP_CAR: + e = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + case BLIP_CHAR: + e = CPools::GetPedPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + case BLIP_OBJECT: + e = CPools::GetObjectPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + default: + return; + } + + if (e) + e->bHasBlip = flag; +} +#endif + +#if 0 +WRAPPER void CRadar::ShowRadarMarker(CVector pos, int16 color, float radius) { EAXJMP(0x4A59C0); } +#else +void CRadar::ShowRadarMarker(CVector pos, int16 color, float radius) { + float f1 = radius * 0.5f; + float f2 = radius * 1.4f; + CVector p1, p2; + + p1 = pos + TheCamera.GetUp()*f1; + p2 = pos + TheCamera.GetUp()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos - TheCamera.GetUp()*f1; + p2 = pos - TheCamera.GetUp()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos + TheCamera.GetRight()*f1; + p2 = pos + TheCamera.GetRight()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos - TheCamera.GetRight()*f1; + p2 = pos - TheCamera.GetRight()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); +} +#endif + +#if 0 +WRAPPER void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha) { EAXJMP(0x4A5870); } +#else +void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha) +{ + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size + 1.0f), y - SCREEN_SCALE_Y(size + 1.0f), SCREEN_SCALE_X(size + 1.0f) + x, SCREEN_SCALE_Y(size + 1.0f) + y), CRGBA(0, 0, 0, alpha)); + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size), y - SCREEN_SCALE_Y(size), SCREEN_SCALE_X(size) + x, SCREEN_SCALE_Y(size) + y), CRGBA(red, green, blue, alpha)); +} +#endif + +#if 1 +WRAPPER void CRadar::Shutdown() { EAXJMP(0x4A3F60); } +#else +void CRadar::Shutdown() +{ + +} +#endif + +#if 1 +WRAPPER void CRadar::StreamRadarSections(const CVector &posn) { EAXJMP(0x4A6B60); } +#else +void CRadar::StreamRadarSections(const CVector &posn) +{ + +} +#endif + +#if 0 +WRAPPER void CRadar::StreamRadarSections(int32 x, int32 y) { EAXJMP(0x4A6100); } +#else +void CRadar::StreamRadarSections(int32 x, int32 y) +{ + for (int i = 0; i < RADAR_NUM_TILES; ++i) { + for (int j = 0; j < RADAR_NUM_TILES; ++j) { + if ((i >= x - 1 && i <= x + 1) && (j >= y - 1 && j <= y + 1)) + RequestMapSection(i, j); + else + RemoveMapSection(i, j); + }; + }; +} +#endif + +#if 0 +WRAPPER void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y) { EAXJMP(0x4A5530); } +#else +void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y) +{ + out.x = in.x - (x * RADAR_TILE_SIZE + WORLD_MIN_X); + out.y = -(in.y - ((RADAR_NUM_TILES - y) * RADAR_TILE_SIZE + WORLD_MIN_Y)); + out.x /= RADAR_TILE_SIZE; + out.y /= RADAR_TILE_SIZE; +} +#endif + +#if 0 +WRAPPER void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in) { EAXJMP(0x4A5300); } +#else +void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; + + s = -sin(TheCamera.GetForward().Heading()); + c = cos(TheCamera.GetForward().Heading()); + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED) { + s = 0.0f; + c = 1.0f; + } + else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON) { + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward(); + forward.Normalise(); // a bit useless... + } + else + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind; + + s = -sin(forward.Heading()); + c = cos(forward.Heading()); + } + + out.x = s * in.y + c * in.x; + out.y = c * in.y - s * in.x; + + out = out * m_RadarRange + vec2DRadarOrigin; +} +#endif + +// Radar space goes from -1.0 to 1.0 in x and y, top right is (1.0, 1.0) +void CRadar::TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in) +{ + // FIX? scale RADAR_LEFT here somehow + out.x = (in.x + 1.0f)*0.5f*SCREEN_SCALE_X(RADAR_WIDTH) + RADAR_LEFT; + out.y = (1.0f - in.y)*0.5f*SCREEN_SCALE_Y(RADAR_HEIGHT) + SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT); +} + +#if 0 +WRAPPER void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in) { EAXJMP(0x4A50D0); } +#else +void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN1 || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWNPED) { + s = 0.0f; + c = 1.0f; + } + else if (TheCamera.GetLookDirection() == LOOKING_FORWARD) { + s = sin(TheCamera.GetForward().Heading()); + c = cos(TheCamera.GetForward().Heading()); + } + else { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIRSTPERSON) { + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward(); + forward.Normalise(); // a bit useless... + } + else + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind; + + s = sin(forward.Heading()); + c = cos(forward.Heading()); + } + + float x = (in.x - vec2DRadarOrigin.x) * (1.0f / m_RadarRange); + float y = (in.y - vec2DRadarOrigin.y) * (1.0f / m_RadarRange); + + out.x = s * y + c * x; + out.y = c * y - s * x; +} +#endif + +#if 0 +WRAPPER void CRadar::GetTextureCorners(int32 x, int32 y, CVector2D *out) { EAXJMP(0x4A61C0); }; +#else +// Transform from section indices to world coordinates +void CRadar::GetTextureCorners(int32 x, int32 y, CVector2D *out) +{ + x = x - RADAR_NUM_TILES/2; + y = -(y - RADAR_NUM_TILES/2); + + // bottom left + out[0].x = RADAR_TILE_SIZE * (x); + out[0].y = RADAR_TILE_SIZE * (y - 1); + + // bottom right + out[1].x = RADAR_TILE_SIZE * (x + 1); + out[1].y = RADAR_TILE_SIZE * (y - 1); + + // top right + out[2].x = RADAR_TILE_SIZE * (x + 1); + out[2].y = RADAR_TILE_SIZE * (y); + + // top left + out[3].x = RADAR_TILE_SIZE * (x); + out[3].y = RADAR_TILE_SIZE * (y); +} +#endif + +#if 0 +WRAPPER void CRadar::ClipRadarTileCoords(int32 &, int32 &) { EAXJMP(0x00); }; +#else +void CRadar::ClipRadarTileCoords(int32 &x, int32 &y) +{ + if (x < 0) + x = 0; + if (x > RADAR_NUM_TILES-1) + x = RADAR_NUM_TILES-1; + if (y < 0) + y = 0; + if (y > RADAR_NUM_TILES-1) + y = RADAR_NUM_TILES-1; +} +#endif + + +#if 0 +WRAPPER bool CRadar::IsPointInsideRadar(const CVector2D &) { EAXJMP(0x4A6160); } +#else +bool CRadar::IsPointInsideRadar(const CVector2D &point) +{ + if (point.x < -1.0f || point.x > 1.0f) return false; + if (point.y < -1.0f || point.y > 1.0f) return false; + return true; +} +#endif + +// clip line p1,p2 against (-1.0, 1.0) in x and y, set out to clipped point closest to p1 +#if 0 +WRAPPER int CRadar::LineRadarBoxCollision(CVector2D &, const CVector2D &, const CVector2D &) { EAXJMP(0x4A6250); } +#else +int CRadar::LineRadarBoxCollision(CVector2D &out, const CVector2D &p1, const CVector2D &p2) +{ + float d1, d2; + float t; + float x, y; + float shortest = 1.0f; + int edge = -1; + + // clip against left edge, x = -1.0 + d1 = -1.0f - p1.x; + d2 = -1.0f - p2.x; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + y = (p2.y - p1.y)*t + p1.y; + if (y >= -1.0f && y <= 1.0f && t <= shortest) { + out.x = -1.0f; + out.y = y; + edge = 3; + shortest = t; + } + } + + // clip against right edge, x = 1.0 + d1 = p1.x - 1.0f; + d2 = p2.x - 1.0f; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + y = (p2.y - p1.y)*t + p1.y; + if (y >= -1.0f && y <= 1.0f && t <= shortest) { + out.x = 1.0f; + out.y = y; + edge = 1; + shortest = t; + } + } + + // clip against top edge, y = -1.0 + d1 = -1.0f - p1.y; + d2 = -1.0f - p2.y; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + x = (p2.x - p1.x)*t + p1.x; + if (x >= -1.0f && x <= 1.0f && t <= shortest) { + out.y = -1.0f; + out.x = x; + edge = 0; + shortest = t; + } + } + + // clip against bottom edge, y = 1.0 + d1 = p1.y - 1.0f; + d2 = p2.y - 1.0f; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + x = (p2.x - p1.x)*t + p1.x; + if (x >= -1.0f && x <= 1.0f && t <= shortest) { + out.y = 1.0f; + out.x = x; + edge = 2; + shortest = t; + } + } + + return edge; +} +#endif + +STARTPATCHES +// InjectHook(0x4A3EF0, CRadar::Initialise, PATCH_JUMP); +// InjectHook(0x4A3F60, CRadar::Shutdown, PATCH_JUMP); +// InjectHook(0x4A4030, CRadar::LoadTextures, PATCH_JUMP); +// InjectHook(0x4A4180, CRadar::GetNewUniqueBlipIndex, PATCH_JUMP); +// InjectHook(0x4A41C0, CRadar::GetActualBlipArrayIndex, PATCH_JUMP); + InjectHook(0x4A4200, CRadar::DrawMap, PATCH_JUMP); + InjectHook(0x4A42F0, CRadar::DrawBlips, PATCH_JUMP); +// InjectHook(0x4A4C70, CRadar::Draw3dMarkers, PATCH_JUMP); + InjectHook(0x4A4F30, CRadar::LimitRadarPoint, PATCH_JUMP); + InjectHook(0x4A4F90, CRadar::CalculateBlipAlpha, PATCH_JUMP); + InjectHook(0x4A5040, CRadar::TransformRadarPointToScreenSpace, PATCH_JUMP); + InjectHook(0x4A50D0, CRadar::TransformRealWorldPointToRadarSpace, PATCH_JUMP); + InjectHook(0x4A5300, CRadar::TransformRadarPointToRealWorldSpace, PATCH_JUMP); + InjectHook(0x4A5530, CRadar::TransformRealWorldToTexCoordSpace, PATCH_JUMP); +// InjectHook(0x4A5590, CRadar::SetCoordBlip, PATCH_JUMP); +// InjectHook(0x4A5640, CRadar::SetEntityBlip, PATCH_JUMP); + InjectHook(0x4A56C0, CRadar::ClearBlipForEntity, PATCH_JUMP); +// InjectHook(0x4A5720, CRadar::ClearBlip, PATCH_JUMP); +// InjectHook(0x4A5770, CRadar::ChangeBlipColour, PATCH_JUMP); +// InjectHook(0x4A57A0, CRadar::ChangeBlipBrightness, PATCH_JUMP); +// InjectHook(0x4A57E0, CRadar::ChangeBlipScale, PATCH_JUMP); +// InjectHook(0x4A5810, CRadar::ChangeBlipDisplay, PATCH_JUMP); +// InjectHook(0x4A5840, CRadar::SetBlipSprite, PATCH_JUMP); + InjectHook(0x4A5870, CRadar::ShowRadarTrace, PATCH_JUMP); + InjectHook(0x4A59C0, CRadar::ShowRadarMarker, PATCH_JUMP); + //InjectHook(0x4A5BB0, CRadar::GetRadarTraceColour, PATCH_JUMP); + InjectHook(0x4A5C60, CRadar::SetRadarMarkerState, PATCH_JUMP); + InjectHook(0x4A5D10, CRadar::DrawRotatingRadarSprite, PATCH_JUMP); + InjectHook(0x4A5EF0, CRadar::DrawRadarSprite, PATCH_JUMP); +// InjectHook(0x4A60E0, CRadar::RemoveRadarSections, PATCH_JUMP); +// InjectHook(0x4A6100, CRadar::StreamRadarSections, PATCH_JUMP); + InjectHook(0x4A64A0, CRadar::ClipRadarPoly, PATCH_JUMP); + InjectHook(0x4A67E0, CRadar::DrawRadarSection, PATCH_JUMP); + InjectHook(0x4A69C0, CRadar::DrawRadarMask, PATCH_JUMP); +// InjectHook(0x4A6B60, CRadar::StreamRadarSections, PATCH_JUMP); + InjectHook(0x4A6C20, CRadar::DrawRadarMap, PATCH_JUMP); +// InjectHook(0x4A6E30, CRadar::SaveAllRadarBlips, PATCH_JUMP); +// InjectHook(0x4A6F30, CRadar::LoadAllRadarBlips, PATCH_JUMP); + + InjectHook(0x4A61C0, CRadar::GetTextureCorners, PATCH_JUMP); + InjectHook(0x4A6160, CRadar::IsPointInsideRadar, PATCH_JUMP); + InjectHook(0x4A6250, CRadar::LineRadarBoxCollision, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Radar.h b/src/core/Radar.h new file mode 100644 index 00000000..e5396a50 --- /dev/null +++ b/src/core/Radar.h @@ -0,0 +1,146 @@ +#pragma once +#include "Sprite2d.h" + +enum eBlipType +{ + BLIP_NONE, + BLIP_CAR, + BLIP_CHAR, + BLIP_OBJECT, + BLIP_COORD, + BLIP_CONTACT_POINT +}; + +enum eBlipDisplay +{ + BLIP_DISPLAY_NEITHER = 0, + BLIP_DISPLAY_MARKER_ONLY = 1, + BLIP_DISPLAY_BLIP_ONLY = 2, + BLIP_DISPLAY_BOTH = 3, +}; + +enum eRadarSprite +{ + RADAR_SPRITE_NONE = 0, + RADAR_SPRITE_ASUKA = 1, + RADAR_SPRITE_BOMB = 2, + RADAR_SPRITE_CAT = 3, + RADAR_SPRITE_CENTRE = 4, + RADAR_SPRITE_COPCAR = 5, + RADAR_SPRITE_DON = 6, + RADAR_SPRITE_EIGHT = 7, + RADAR_SPRITE_EL = 8, + RADAR_SPRITE_ICE = 9, + RADAR_SPRITE_JOEY = 10, + RADAR_SPRITE_KENJI = 11, + RADAR_SPRITE_LIZ = 12, + RADAR_SPRITE_LUIGI = 13, + RADAR_SPRITE_NORTH = 14, + RADAR_SPRITE_RAY = 15, + RADAR_SPRITE_SAL = 16, + RADAR_SPRITE_SAVE = 17, + RADAR_SPRITE_SPRAY = 18, + RADAR_SPRITE_TONY = 19, + RADAR_SPRITE_WEAPON = 20, + RADAR_SPRITE_COUNT = 21, +}; + +struct CBlip +{ + int32 m_nColor; + int16 m_eBlipType; // eBlipType + int32 m_nEntityHandle; + CVector2D m_vec2DPos; + CVector m_vecPos; + int16 m_BlipIndex; + bool m_bDim; + bool m_bInUse; + float m_Radius; + int16 m_wScale; + int16 m_eBlipDisplay; // eBlipDisplay + int16 m_IconID; // eRadarSprite +}; +static_assert(sizeof(CBlip) == 0x30, "CBlip: error"); + +// Values for screen space +#define RADAR_LEFT (40.0f) +#define RADAR_BOTTOM (47.0f) +#define RADAR_WIDTH (94.0f) +#define RADAR_HEIGHT (76.0f) + +class CRadar +{ +public: + static float &m_RadarRange; + static CBlip *ms_RadarTrace; //[NUMRADARBLIPS] + static CSprite2d *AsukaSprite; + static CSprite2d *BombSprite; + static CSprite2d *CatSprite; + static CSprite2d *CentreSprite; + static CSprite2d *CopcarSprite; + static CSprite2d *DonSprite; + static CSprite2d *EightSprite; + static CSprite2d *ElSprite; + static CSprite2d *IceSprite; + static CSprite2d *JoeySprite; + static CSprite2d *KenjiSprite; + static CSprite2d *LizSprite; + static CSprite2d *LuigiSprite; + static CSprite2d *NorthSprite; + static CSprite2d *RaySprite; + static CSprite2d *SalSprite; + static CSprite2d *SaveSprite; + static CSprite2d *SpraySprite; + static CSprite2d *TonySprite; + static CSprite2d *WeaponSprite; + static CSprite2d *RadarSprites[21]; + +public: + static int CalculateBlipAlpha(float dist); + static void ChangeBlipBrightness(int32 i, int32 bright); + static void ChangeBlipColour(int32 i); + static void ChangeBlipDisplay(int32 i, int16 flag); + static void ChangeBlipScale(int32 i, int16 scale); + static void ClearBlip(int32 i); + static void ClearBlipForEntity(int16 type, int32 id); + static int ClipRadarPoly(CVector2D *out, const CVector2D *in); + static bool DisplayThisBlip(int32 i); + static void Draw3dMarkers(); + static void DrawBlips(); + static void DrawMap(); + static void DrawRadarMap(); + static void DrawRadarMask(); + static void DrawRadarSection(int32 x, int32 y); + static void DrawRadarSprite(int32 sprite, float x, float y, int32 alpha); + static void DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha); + static int32 GetActualBlipArray(int32 i); + static int32 GetNewUniqueBlipIndex(int32 i); + static int32 GetRadarTraceColour(int32 color, bool bright); + static void Initialise(); + static float LimitRadarPoint(CVector2D &point); + static void LoadAllRadarBlips(int32); + static void LoadTextures(); + static void RemoveRadarSections(); + static void RemoveMapSection(int32 x, int32 y); + static void RequestMapSection(int32 x, int32 y); + static void SaveAllRadarBlips(int32); + static void SetBlipSprite(int32 i, int32 icon); + static int SetCoordBlip(int32 type, CVector pos, int32 flag); + static int SetEntityBlip(int32 type, CVector pos, int32 color, int32 flag); + static void SetRadarMarkerState(int32 i, int32 flag); + static void ShowRadarMarker(CVector pos, int16 color, float radius); + static void ShowRadarTrace(float x, float y, uint32 size, uint32 red, uint32 green, uint32 blue, uint32 alpha); + static void Shutdown(); + static void StreamRadarSections(const CVector &posn); + static void StreamRadarSections(int32 x, int32 y); + static void TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y); + static void TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in); + static void TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in); + static void TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in); + + // no in CRadar in the game: + static void GetTextureCorners(int32 x, int32 y, CVector2D *out); + static void ClipRadarTileCoords(int32 &x, int32 &y); + static bool IsPointInsideRadar(const CVector2D &); + static int LineRadarBoxCollision(CVector2D &, const CVector2D &, const CVector2D &); +}; diff --git a/src/core/References.cpp b/src/core/References.cpp new file mode 100644 index 00000000..e87f0fd5 --- /dev/null +++ b/src/core/References.cpp @@ -0,0 +1,65 @@ +#include "common.h" +#include "patcher.h" +#include "World.h" +#include "Vehicle.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "References.h" + +CReference *CReferences::aRefs = (CReference*)0x70BBE0; //[NUMREFERENCES]; +CReference *&CReferences::pEmptyList = *(CReference**)0x8F1AF8; + +void +CReferences::Init(void) +{ + int i; + pEmptyList = &aRefs[0]; + for(i = 0; i < NUMREFERENCES; i++){ + aRefs[i].pentity = nil; + aRefs[i].next = &aRefs[i+1]; + } + aRefs[NUMREFERENCES-1].next = nil; +} + +void +CReferences::RemoveReferencesToPlayer(void) +{ + if(FindPlayerVehicle()) + FindPlayerVehicle()->ResolveReferences(); + if(FindPlayerPed()) + FindPlayerPed()->ResolveReferences(); +} + +void +CReferences::PruneAllReferencesInWorld(void) +{ + int i; + CEntity *e; + + i = CPools::GetPedPool()->GetSize(); + while(--i >= 0){ + e = CPools::GetPedPool()->GetSlot(i); + if(e) + e->PruneReferences(); + } + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + e = CPools::GetVehiclePool()->GetSlot(i); + if(e) + e->PruneReferences(); + } + + i = CPools::GetObjectPool()->GetSize(); + while(--i >= 0){ + e = CPools::GetObjectPool()->GetSlot(i); + if(e) + e->PruneReferences(); + } +} + +STARTPATCHES + InjectHook(0x4A7350, CReferences::Init, PATCH_JUMP); + InjectHook(0x4A7570, CReferences::RemoveReferencesToPlayer, PATCH_JUMP); + InjectHook(0x4A75A0, CReferences::PruneAllReferencesInWorld, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/References.h b/src/core/References.h new file mode 100644 index 00000000..6476e243 --- /dev/null +++ b/src/core/References.h @@ -0,0 +1,20 @@ +#pragma once + +class CEntity; + +struct CReference +{ + CReference *next; + CEntity **pentity; +}; + +class CReferences +{ +public: + static CReference *aRefs; //[NUMREFERENCES]; + static CReference *&pEmptyList; + + static void Init(void); + static void RemoveReferencesToPlayer(void); + static void PruneAllReferencesInWorld(void); +}; diff --git a/src/core/RwClumpRead.cpp b/src/core/RwClumpRead.cpp new file mode 100644 index 00000000..c9f027e7 --- /dev/null +++ b/src/core/RwClumpRead.cpp @@ -0,0 +1,230 @@ +#include "common.h" +#include "patcher.h" + +struct rpGeometryList +{ + RpGeometry **geometries; + int32 numGeoms; +}; + +struct rpAtomicBinary +{ + RwInt32 frameIndex; + RwInt32 geomIndex; + RwInt32 flags; + RwInt32 unused; +}; + +static int32 numberGeometrys; +static int32 streamPosition; +static rpGeometryList gGeomList; +static rwFrameList gFrameList; +static RpClumpChunkInfo gClumpInfo; + +rpGeometryList* +GeometryListStreamRead1(RwStream *stream, rpGeometryList *geomlist) +{ + int i; + RwUInt32 size, version; + RwInt32 numGeoms; + + numberGeometrys = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size == 4); + if(RwStreamRead(stream, &numGeoms, 4) != 4) + return nil; + + numberGeometrys = numGeoms/2; + geomlist->numGeoms = numGeoms; + if(geomlist->numGeoms > 0){ + geomlist->geometries = (RpGeometry**)RwMalloc(geomlist->numGeoms * sizeof(RpGeometry*)); + if(geomlist->geometries == nil) + return nil; + memset(geomlist->geometries, 0, geomlist->numGeoms * sizeof(RpGeometry*)); + }else + geomlist->geometries = nil; + + for(i = 0; i < numberGeometrys; i++){ + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)) + return nil; + geomlist->geometries[i] = RpGeometryStreamRead(stream); + if(geomlist->geometries[i] == nil) + return nil; + } + + return geomlist; +} + +rpGeometryList* +GeometryListStreamRead2(RwStream *stream, rpGeometryList *geomlist) +{ + int i; + RwUInt32 version; + + for(i = numberGeometrys; i < geomlist->numGeoms; i++){ + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)) + return nil; + geomlist->geometries[i] = RpGeometryStreamRead(stream); + if(geomlist->geometries[i] == nil) + return nil; + } + + return geomlist; +} + +void +GeometryListDeinitialize(rpGeometryList *geomlist) +{ + int i; + + for(i = 0; i < geomlist->numGeoms; i++) + if(geomlist->geometries[i]) + RpGeometryDestroy(geomlist->geometries[i]); + + if(geomlist->numGeoms){ + RwFree(geomlist->geometries); + geomlist->numGeoms = 0; + } +} + +RpAtomic* +ClumpAtomicStreamRead(RwStream *stream, rwFrameList *frmList, rpGeometryList *geomList) +{ + RwUInt32 size, version; + rpAtomicBinary a; + RpAtomic *atomic; + + numberGeometrys = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size <= sizeof(rpAtomicBinary)); + if(RwStreamRead(stream, &a, size) != size) + return nil; + + atomic = RpAtomicCreate(); + if(atomic == nil) + return nil; + + RpAtomicSetFlags(atomic, a.flags); + + if(frmList->numFrames){ + assert(a.frameIndex < frmList->numFrames); + RpAtomicSetFrame(atomic, frmList->frames[a.frameIndex]); + } + + if(geomList->numGeoms){ + assert(a.geomIndex < geomList->numGeoms); + RpAtomicSetGeometry(atomic, geomList->geometries[a.geomIndex], 0); + }else{ + RpGeometry *geom; + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)){ + RpAtomicDestroy(atomic); + return nil; + } + geom = RpGeometryStreamRead(stream); + if(geom == nil){ + RpAtomicDestroy(atomic); + return nil; + } + RpAtomicSetGeometry(atomic, geom, 0); + RpGeometryDestroy(geom); + } + + return atomic; +} + +bool +RpClumpGtaStreamRead1(RwStream *stream) +{ + RwUInt32 size, version; + + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return false; + if(version >= 0x33000){ + assert(size == 12); + if(RwStreamRead(stream, &gClumpInfo, 12) != 12) + return false; + }else{ + assert(size == 4); + if(RwStreamRead(stream, &gClumpInfo, 4) != 4) + return false; + } + + if(!RwStreamFindChunk(stream, rwID_FRAMELIST, nil, &version)) + return false; + if(_rwFrameListStreamRead(stream, &gFrameList) == nil) + return false; + + if(!RwStreamFindChunk(stream, rwID_GEOMETRYLIST, nil, &version)){ + rwFrameListDeinitialize(&gFrameList); + return false; + } + if(GeometryListStreamRead1(stream, &gGeomList) == nil){ + rwFrameListDeinitialize(&gFrameList); + return false; + } + streamPosition = stream->Type.memory.position; + return true; +} + +RpClump* +RpClumpGtaStreamRead2(RwStream *stream) +{ + int i; + RwUInt32 version; + RpAtomic *atomic; + RpClump *clump; + + clump = RpClumpCreate(); + if(clump == nil) + return nil; + + RwStreamSkip(stream, streamPosition - stream->Type.memory.position); + + if(GeometryListStreamRead2(stream, &gGeomList) == nil){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + RpClumpSetFrame(clump, gFrameList.frames[0]); + + for(i = 0; i < gClumpInfo.numAtomics; i++){ + if(!RwStreamFindChunk(stream, rwID_ATOMIC, nil, &version)){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + atomic = ClumpAtomicStreamRead(stream, &gFrameList, &gGeomList); + if(atomic == nil){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + RpClumpAddAtomic(clump, atomic); + } + + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + return clump; +} + +void +RpClumpGtaCancelStream(void) +{ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + gFrameList.numFrames = 0; +} + +STARTPATCHES + InjectHook(0x526060, RpClumpGtaStreamRead1, PATCH_JUMP); + InjectHook(0x526180, RpClumpGtaStreamRead2, PATCH_JUMP); + InjectHook(0x5262D0, RpClumpGtaCancelStream, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/RwHelper.cpp b/src/core/RwHelper.cpp new file mode 100644 index 00000000..8dade266 --- /dev/null +++ b/src/core/RwHelper.cpp @@ -0,0 +1,356 @@ +#define WITHD3D +#include "common.h" +#include "patcher.h" +#include "Timecycle.h" +#include "skeleton.h" + +void * +RwMallocAlign(RwUInt32 size, RwUInt32 align) +{ + void *mem = (void *)malloc(size + align); + + ASSERT(mem != nil); + + void *addr = (void *)((((RwUInt32)mem) + align) & ~(align - 1)); + + ASSERT(addr != nil); + + *(((void **)addr) - 1) = mem; + + return addr; +} + +void +RwFreeAlign(void *mem) +{ + ASSERT(mem != nil); + + void *addr = *(((void **)mem) - 1); + + ASSERT(addr != nil); + + free(addr); +} + +void +DefinedState(void) +{ + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSWRAP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEALPHAPRIMITIVEBUFFER, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEBORDERCOLOR, (void*)RWRGBALONG(0, 0, 0, 255)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, + (void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + + // D3D stuff + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); + RwD3D8SetRenderState(D3DRS_ALPHAREF, 2); +} + +RwFrame* +GetFirstFrameCallback(RwFrame *child, void *data) +{ + *(RwFrame**)data = child; + return nil; +} + +RwFrame* +GetFirstChild(RwFrame *frame) +{ + RwFrame *child; + + child = nil; + RwFrameForAllChildren(frame, GetFirstFrameCallback, &child); + return child; +} + +RwObject* +GetFirstObjectCallback(RwObject *object, void *data) +{ + *(RwObject**)data = object; + return nil; +} + +RwObject* +GetFirstObject(RwFrame *frame) +{ + RwObject *obj; + + obj = nil; + RwFrameForAllObjects(frame, GetFirstObjectCallback, &obj); + return obj; +} + +RpAtomic* +GetFirstAtomicCallback(RpAtomic *atm, void *data) +{ + *(RpAtomic**)data = atm; + return nil; +} + +RpAtomic* +GetFirstAtomic(RpClump *clump) +{ + RpAtomic *atm; + + atm = nil; + RpClumpForAllAtomics(clump, GetFirstAtomicCallback, &atm); + return atm; +} + +RwTexture* +GetFirstTextureCallback(RwTexture *tex, void *data) +{ + *(RwTexture**)data = tex; + return nil; +} + +RwTexture* +GetFirstTexture(RwTexDictionary *txd) +{ + RwTexture *tex; + + tex = nil; + RwTexDictionaryForAllTextures(txd, GetFirstTextureCallback, &tex); + return tex; +} + +void +CameraSize(RwCamera * camera, RwRect * rect, + RwReal viewWindow, RwReal aspectRatio) +{ + if (camera) + { + RwVideoMode videoMode; + RwRect r; + RwRect origSize = { 0, 0, 0, 0 }; // FIX just to make the compier happy + RwV2d vw; + + RwEngineGetVideoModeInfo(&videoMode, + RwEngineGetCurrentVideoMode()); + + origSize.w = RwRasterGetWidth(RwCameraGetRaster(camera)); + origSize.h = RwRasterGetHeight(RwCameraGetRaster(camera)); + + if (!rect) + { + if (videoMode.flags & rwVIDEOMODEEXCLUSIVE) + { + /* For full screen applications, resizing the camera just doesn't + * make sense, use the video mode size. + */ + + r.x = r.y = 0; + r.w = videoMode.width; + r.h = videoMode.height; + rect = &r; + } + else + { + /* + rect not specified - reuse current values + */ + r.w = RwRasterGetWidth(RwCameraGetRaster(camera)); + r.h = RwRasterGetHeight(RwCameraGetRaster(camera)); + r.x = r.y = 0; + rect = &r; + } + } + + if (( origSize.w != rect->w ) && ( origSize.h != rect->h )) + { + RwRaster *raster; + RwRaster *zRaster; + + /* + * Destroy rasters... + */ + + raster = RwCameraGetRaster(camera); + if( raster ) + { + RwRasterDestroy(raster); + } + + zRaster = RwCameraGetZRaster(camera); + if( zRaster ) + { + RwRasterDestroy(zRaster); + } + + /* + * Create new rasters... + */ + + raster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA); + zRaster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER); + + if( raster && zRaster ) + { + RwCameraSetRaster(camera, raster); + RwCameraSetZRaster(camera, zRaster); + } + else + { + if( raster ) + { + RwRasterDestroy(raster); + } + + if( zRaster ) + { + RwRasterDestroy(zRaster); + } + + rect->x = origSize.x; + rect->y = origSize.y; + rect->w = origSize.w; + rect->h = origSize.h; + + /* + * Use default values... + */ + raster = + RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA); + + zRaster = + RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER); + + RwCameraSetRaster(camera, raster); + RwCameraSetZRaster(camera, zRaster); + } + } + + /* Figure out the view window */ + if (videoMode.flags & rwVIDEOMODEEXCLUSIVE) + { + /* derive ratio from aspect ratio */ + vw.x = viewWindow; + vw.y = viewWindow / aspectRatio; + } + else + { + /* derive from pixel ratios */ + if (rect->w > rect->h) + { + vw.x = viewWindow; + vw.y = (rect->h * viewWindow) / rect->w; + } + else + { + vw.x = (rect->w * viewWindow) / rect->h; + vw.y = viewWindow; + } + } + + RwCameraSetViewWindow(camera, &vw); + + RsGlobal.width = rect->w; + RsGlobal.height = rect->h; + } + + return; +} + +void +CameraDestroy(RwCamera *camera) +{ + RwRaster *raster, *tmpRaster; + RwFrame *frame; + + if (camera) + { + frame = RwCameraGetFrame(camera); + if (frame) + { + RwFrameDestroy(frame); + } + + raster = RwCameraGetRaster(camera); + if (raster) + { + tmpRaster = RwRasterGetParent(raster); + + RwRasterDestroy(raster); + + if ((tmpRaster != nil) && (tmpRaster != raster)) + { + RwRasterDestroy(tmpRaster); + } + } + + raster = RwCameraGetZRaster(camera); + if (raster) + { + tmpRaster = RwRasterGetParent(raster); + + RwRasterDestroy(raster); + + if ((tmpRaster != nil) && (tmpRaster != raster)) + { + RwRasterDestroy(tmpRaster); + } + } + + RwCameraDestroy(camera); + } + + return; +} + +RwCamera * +CameraCreate(RwInt32 width, RwInt32 height, RwBool zBuffer) +{ + RwCamera *camera; + + camera = RwCameraCreate(); + + if (camera) + { + RwCameraSetFrame(camera, RwFrameCreate()); + RwCameraSetRaster(camera, + RwRasterCreate(0, 0, 0, rwRASTERTYPECAMERA)); + + if (zBuffer) + { + RwCameraSetZRaster(camera, + RwRasterCreate(0, 0, 0, + rwRASTERTYPEZBUFFER)); + } + + /* now check that everything is valid */ + if (RwCameraGetFrame(camera) && + RwCameraGetRaster(camera) && + RwRasterGetParent(RwCameraGetRaster(camera)) && + (!zBuffer || (RwCameraGetZRaster(camera) && + RwRasterGetParent(RwCameraGetZRaster + (camera))))) + { + /* everything OK */ + return (camera); + } + } + + /* if we're here then an error must have occurred so clean up */ + + CameraDestroy(camera); + return (nil); +} + +STARTPATCHES + //InjectHook(0x526450, GetFirstObjectCallback, PATCH_JUMP); + InjectHook(0x526460, GetFirstObject, PATCH_JUMP); + InjectHook(0x527170, CameraSize, PATCH_JUMP); + InjectHook(0x527340, CameraDestroy, PATCH_JUMP); + InjectHook(0x5273B0, CameraCreate, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/RwHelper.h b/src/core/RwHelper.h new file mode 100644 index 00000000..ef20467d --- /dev/null +++ b/src/core/RwHelper.h @@ -0,0 +1,27 @@ +#pragma once + +void *RwMallocAlign(RwUInt32 size, RwUInt32 align); +void RwFreeAlign(void *mem); + +void DefinedState(void); +RwFrame *GetFirstChild(RwFrame *frame); +RwObject *GetFirstObject(RwFrame *frame); +RpAtomic *GetFirstAtomic(RpClump *clump); +RwTexture *GetFirstTexture(RwTexDictionary *txd); + +RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict); + +bool RpClumpGtaStreamRead1(RwStream *stream); +RpClump *RpClumpGtaStreamRead2(RwStream *stream); +void RpClumpGtaCancelStream(void); + +void CameraSize(RwCamera *camera, + RwRect *rect, + RwReal viewWindow, + RwReal aspectRatio); +void CameraDestroy(RwCamera *camera); +RwCamera *CameraCreate(RwInt32 width, + RwInt32 height, + RwBool zBuffer); diff --git a/src/core/RwMatFX.cpp b/src/core/RwMatFX.cpp new file mode 100644 index 00000000..5fd00c54 --- /dev/null +++ b/src/core/RwMatFX.cpp @@ -0,0 +1,212 @@ +#define WITHD3D +#include "common.h" +#include "patcher.h" + +struct MatFXNothing { int pad[5]; int effect; }; + +struct MatFXBump +{ + RwFrame *bumpFrame; + RwTexture *bumpedTex; + RwTexture *bumpTex; + float negBumpCoefficient; + int pad; + int effect; +}; + +struct MatFXEnv +{ + RwFrame *envFrame; + RwTexture *envTex; + float envCoeff; + int envFBalpha; + int pad; + int effect; +}; + +struct MatFXDual +{ + RwTexture *dualTex; + RwInt32 srcBlend; + RwInt32 dstBlend; +}; + + +struct MatFX +{ + union { + MatFXNothing n; + MatFXBump b; + MatFXEnv e; + MatFXDual d; + } fx[2]; + int effects; +}; + +int &MatFXMaterialDataOffset = *(int*)0x66188C; +int &MatFXAtomicDataOffset = *(int*)0x66189C; + +#ifdef PS2_MATFX + +void +_rpMatFXD3D8AtomicMatFXDefaultRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture) +{ + if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture) + RwD3D8SetTexture(texture, 0); + else + RwD3D8SetTexture(nil, 0); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF)); + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, inst->vertexAlpha != 0); + RwD3D8SetPixelShader(0); + RwD3D8SetVertexShader(inst->vertexShader); + RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride); + + if(inst->indexBuffer){ + RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex); + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + }else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); +} + +// map [-1; -1] -> [0; 1], flip V +static RwMatrix scalenormal = { + { 0.5f, 0.0f, 0.0f }, 0, + { 0.0f, -0.5f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.5f, 0.5f, 0.0f }, 0, + +}; + +// flipped U for PS2 +static RwMatrix scalenormal_flipU = { + { -0.5f, 0.0f, 0.0f }, 0, + { 0.0f, -0.5f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.5f, 0.5f, 0.0f }, 0, + +}; + +void +ApplyEnvMapTextureMatrix(RwTexture *tex, int n, RwFrame *frame) +{ + RwD3D8SetTexture(tex, n); + RwD3D8SetTextureStageState(n, D3DRS_ALPHAREF, 2); + RwD3D8SetTextureStageState(n, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL); + if(frame){ + RwMatrix *envframemat = RwMatrixCreate(); + RwMatrix *tmpmat = RwMatrixCreate(); + RwMatrix *envmat = RwMatrixCreate(); + + RwMatrixInvert(envframemat, RwFrameGetLTM(frame)); + // PS2 + // can this be simplified? + *tmpmat = *RwFrameGetLTM(RwCameraGetFrame((RwCamera*)RWSRCGLOBAL(curCamera))); + RwV3dNegate(&tmpmat->right, &tmpmat->right); + tmpmat->flags = 0; + tmpmat->pos.x = 0.0f; + tmpmat->pos.y = 0.0f; + tmpmat->pos.z = 0.0f; + RwMatrixMultiply(envmat, tmpmat, envframemat); + *tmpmat = *envmat; + // important because envframemat can have a translation that we don't like + tmpmat->pos.x = 0.0f; + tmpmat->pos.y = 0.0f; + tmpmat->pos.z = 0.0f; + // for some reason we flip in U as well + RwMatrixMultiply(envmat, tmpmat, &scalenormal_flipU); + + RwD3D8SetTransform(D3DTS_TEXTURE0+n, envmat); + + RwMatrixDestroy(envmat); + RwMatrixDestroy(tmpmat); + RwMatrixDestroy(envframemat); + }else + RwD3D8SetTransform(D3DTS_TEXTURE0+n, &scalenormal); +} + +void +_rpMatFXD3D8AtomicMatFXEnvRender_ps2(RxD3D8InstanceData *inst, int flags, int sel, RwTexture *texture, RwTexture *envMap) +{ + MatFX *matfx = *RWPLUGINOFFSET(MatFX*, inst->material, MatFXMaterialDataOffset); + MatFXEnv *env = &matfx->fx[sel].e; + + uint8 intens = (uint8)(env->envCoeff*255.0f); + + if(intens == 0 || envMap == nil){ + if(sel == 0) + _rpMatFXD3D8AtomicMatFXDefaultRender(inst, flags, texture); + return; + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF)); + if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture) + RwD3D8SetTexture(texture, 0); + else + RwD3D8SetTexture(nil, 0); + RwD3D8SetVertexShader(inst->vertexShader); + RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride); + RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex); + if(inst->indexBuffer) + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); + + // Effect pass + + ApplyEnvMapTextureMatrix(envMap, 0, env->envFrame); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwUInt32 src, dst, lighting, zwrite, fog, fogcol; + RwRenderStateGet(rwRENDERSTATESRCBLEND, &src); + RwRenderStateGet(rwRENDERSTATEDESTBLEND, &dst); + + // This is of course not using framebuffer alpha, + // but if the diffuse texture had no alpha, the result should actually be rather the same + if(env->envFBalpha) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + RwD3D8GetRenderState(D3DRS_FOGENABLE, &fog); + RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE); + if(fog){ + RwD3D8GetRenderState(D3DRS_FOGCOLOR, &fogcol); + RwD3D8SetRenderState(D3DRS_FOGCOLOR, 0); + } + + D3DCOLOR texfactor = D3DCOLOR_RGBA(intens, intens, intens, intens); + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, texfactor); + RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); + RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR); + // alpha unused + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); + + if(inst->indexBuffer) + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); + + // Reset states + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)src); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)dst); + RwD3D8SetRenderState(D3DRS_LIGHTING, lighting); + RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zwrite); + if(fog) + RwD3D8SetRenderState(D3DRS_FOGCOLOR, fogcol); + RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0); + RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); +} + +STARTPATCHES + InjectHook(0x5CF6C0, _rpMatFXD3D8AtomicMatFXEnvRender_ps2, PATCH_JUMP); +ENDPATCHES + +#endif diff --git a/src/core/RwTexRead.cpp b/src/core/RwTexRead.cpp new file mode 100644 index 00000000..a1a7050a --- /dev/null +++ b/src/core/RwTexRead.cpp @@ -0,0 +1,126 @@ +#include "common.h" +#include "patcher.h" + +RwTexture* +RwTextureGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version)) + return nil; + + // TODO: unused timing + + if(!RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, &tex, size))) + return nil; + + return tex; +} + +RwTexture* +destroyTexture(RwTexture *texture, void *data) +{ + RwTextureDestroy(texture); + return texture; +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwInt32 numTextures; + RwTexDictionary *texDict; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size == 4); + if(RwStreamRead(stream, &numTextures, size) != size) + return nil; + + texDict = RwTexDictionaryCreate(); + if(texDict == nil) + return nil; + + while(numTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +static int32 numberTextures = -1; +static int32 streamPosition; + +RwTexDictionary* +RwTexDictionaryGtaStreamRead1(RwStream *stream) +{ + RwUInt32 size, version; + RwInt32 numTextures; + RwTexDictionary *texDict; + RwTexture *tex; + + numberTextures = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size == 4); + if(RwStreamRead(stream, &numTextures, size) != size) + return nil; + + texDict = RwTexDictionaryCreate(); + if(texDict == nil) + return nil; + + numberTextures = numTextures/2; + + while(numTextures > numberTextures){ + numTextures--; + + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + numberTextures = numTextures; + streamPosition = stream->Type.memory.position; + + return texDict; +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict) +{ + RwTexture *tex; + + RwStreamSkip(stream, streamPosition - stream->Type.memory.position); + + while(numberTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +STARTPATCHES + InjectHook(0x592380, RwTextureGtaStreamRead, PATCH_JUMP); + InjectHook(0x5924A0, RwTexDictionaryGtaStreamRead, PATCH_JUMP); + InjectHook(0x592550, RwTexDictionaryGtaStreamRead1, PATCH_JUMP); + InjectHook(0x592650, RwTexDictionaryGtaStreamRead2, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Stats.cpp b/src/core/Stats.cpp new file mode 100644 index 00000000..921586bb --- /dev/null +++ b/src/core/Stats.cpp @@ -0,0 +1,13 @@ +#include "common.h" +#include "Stats.h" + +int32 &CStats::DaysPassed = *(int32*)0x8F2BB8; +int32 &CStats::HeadShots = *(int32*)0x8F647C; +bool& CStats::CommercialPassed = *(bool*)0x8F4334; +int32 &CStats::NumberKillFrenziesPassed = *(int32*)0x8E287C; +int32 &CStats::PeopleKilledByOthers = *(int32*)0x8E2C50; + +void CStats::AnotherKillFrenzyPassed() +{ + ++NumberKillFrenziesPassed; +}
\ No newline at end of file diff --git a/src/core/Stats.h b/src/core/Stats.h new file mode 100644 index 00000000..a5670020 --- /dev/null +++ b/src/core/Stats.h @@ -0,0 +1,14 @@ +#pragma once + +class CStats +{ +public: + static int32 &DaysPassed; + static int32 &HeadShots; + static bool& CommercialPassed; + static int32 &NumberKillFrenziesPassed; + static int32 &PeopleKilledByOthers; + +public: + static void AnotherKillFrenzyPassed(); +};
\ No newline at end of file diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp new file mode 100644 index 00000000..f8ab19d4 --- /dev/null +++ b/src/core/Streaming.cpp @@ -0,0 +1,2509 @@ +#include "common.h" +#include "patcher.h" +#include "Pad.h" +#include "Hud.h" +#include "Text.h" +#include "Clock.h" +#include "Renderer.h" +#include "ModelInfo.h" +#include "TxdStore.h" +#include "ModelIndices.h" +#include "Pools.h" +#include "Directory.h" +#include "RwHelper.h" +#include "World.h" +#include "Entity.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "Zones.h" +#include "ZoneCull.h" +#include "Radar.h" +#include "Camera.h" +#include "Record.h" +#include "CarCtrl.h" +#include "Population.h" +#include "Gangs.h" +#include "CutsceneMgr.h" +#include "CdStream.h" +#include "Streaming.h" + +bool &CStreaming::ms_disableStreaming = *(bool*)0x95CD6E; +bool &CStreaming::ms_bLoadingBigModel = *(bool*)0x95CDB0; +int32 &CStreaming::ms_numModelsRequested = *(int32*)0x8E2C10; +CStreamingInfo *CStreaming::ms_aInfoForModel = (CStreamingInfo*)0x6C7088; +CStreamingInfo &CStreaming::ms_startLoadedList = *(CStreamingInfo*)0x942F60; +CStreamingInfo &CStreaming::ms_endLoadedList = *(CStreamingInfo*)0x8F1AC0; +CStreamingInfo &CStreaming::ms_startRequestedList = *(CStreamingInfo*)0x8F1B3C; +CStreamingInfo &CStreaming::ms_endRequestedList = *(CStreamingInfo*)0x940738; +int32 &CStreaming::ms_oldSectorX = *(int32*)0x8F2C84; +int32 &CStreaming::ms_oldSectorY = *(int32*)0x8F2C88; +int32 &CStreaming::ms_streamingBufferSize = *(int32*)0x942FB0; +int8 **CStreaming::ms_pStreamingBuffer = (int8**)0x87F818; +int32 &CStreaming::ms_memoryUsed = *(int32*)0x940568; +CStreamingChannel *CStreaming::ms_channel = (CStreamingChannel*)0x727EE0; +int32 &CStreaming::ms_channelError = *(int32*)0x880DB8; +int32 &CStreaming::ms_numVehiclesLoaded = *(int32*)0x8F2C80; +int32 *CStreaming::ms_vehiclesLoaded = (int32*)0x773560; +int32 &CStreaming::ms_lastVehicleDeleted = *(int32*)0x95CBF8; +CDirectory *&CStreaming::ms_pExtraObjectsDir = *(CDirectory**)0x95CB90; +int32 &CStreaming::ms_numPriorityRequests = *(int32*)0x8F31C4; +bool &CStreaming::ms_hasLoadedLODs = *(bool*)0x95CD47; +int32 &CStreaming::ms_currentPedGrp = *(int32*)0x8F2BBC; +int32 CStreaming::ms_currentPedLoading; +int32 CStreaming::ms_lastCullZone; +uint16 &CStreaming::ms_loadedGangs = *(uint16*)0x95CC60; +uint16 &CStreaming::ms_loadedGangCars = *(uint16*)0x95CC2E; +int32 *CStreaming::ms_imageOffsets = (int32*)0x6E60A0; +int32 &CStreaming::ms_lastImageRead = *(int32*)0x880E2C; +int32 &CStreaming::ms_imageSize = *(int32*)0x8F1A34; +uint32 &CStreaming::ms_memoryAvailable = *(uint32*)0x880F8C; + +int32 &desiredNumVehiclesLoaded = *(int32*)0x5EC194; + +CEntity *&pIslandLODindustEntity = *(CEntity**)0x6212DC; +CEntity *&pIslandLODcomIndEntity = *(CEntity**)0x6212E0; +CEntity *&pIslandLODcomSubEntity = *(CEntity**)0x6212E4; +CEntity *&pIslandLODsubIndEntity = *(CEntity**)0x6212E8; +CEntity *&pIslandLODsubComEntity = *(CEntity**)0x6212EC; +int32 &islandLODindust = *(int32*)0x6212C8; +int32 &islandLODcomInd = *(int32*)0x6212CC; +int32 &islandLODcomSub = *(int32*)0x6212D0; +int32 &islandLODsubInd = *(int32*)0x6212D4; +int32 &islandLODsubCom = *(int32*)0x6212D8; + +bool +CStreamingInfo::GetCdPosnAndSize(uint32 &posn, uint32 &size) +{ + if(m_size == 0) + return false; + posn = m_position; + size = m_size; + return true; +} + +void +CStreamingInfo::SetCdPosnAndSize(uint32 posn, uint32 size) +{ + m_position = posn; + m_size = size; +} + +void +CStreamingInfo::AddToList(CStreamingInfo *link) +{ + // Insert this after link + m_next = link->m_next; + m_prev = link; + link->m_next = this; + m_next->m_prev = this; +} + +void +CStreamingInfo::RemoveFromList(void) +{ + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + m_next = nil; + m_prev = nil; +} + +void +CStreaming::Init(void) +{ + int i; + + for(i = 0; i < NUMSTREAMINFO; i++){ + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + ms_aInfoForModel[i].m_next = nil; + ms_aInfoForModel[i].m_prev = nil; + ms_aInfoForModel[i].m_nextID = -1; + ms_aInfoForModel[i].m_size = 0; + ms_aInfoForModel[i].m_position = 0; + } + + ms_channelError = -1; + + // init lists + + ms_startLoadedList.m_next = &ms_endLoadedList; + ms_startLoadedList.m_prev = nil; + ms_endLoadedList.m_prev = &ms_startLoadedList; + ms_endLoadedList.m_next = nil; + + ms_startRequestedList.m_next = &ms_endRequestedList; + ms_startRequestedList.m_prev = nil; + ms_endRequestedList.m_prev = &ms_startRequestedList; + ms_endRequestedList.m_next = nil; + + // init misc + + ms_oldSectorX = 0; + ms_oldSectorY = 0; + ms_streamingBufferSize = 0; + ms_disableStreaming = false; + ms_memoryUsed = 0; + ms_bLoadingBigModel = false; + + // init channels + + ms_channel[0].state = CHANNELSTATE_IDLE; + ms_channel[1].state = CHANNELSTATE_IDLE; + for(i = 0; i < 4; i++){ + ms_channel[0].streamIds[i] = -1; + ms_channel[0].offsets[i] = -1; + ms_channel[1].streamIds[i] = -1; + ms_channel[1].offsets[i] = -1; + } + + // init stream info, mark things that are already loaded + + for(i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if(mi && mi->GetRwObject()){ + ms_aInfoForModel[i].m_loadState = STREAMSTATE_LOADED; + ms_aInfoForModel[i].m_flags = STREAMFLAGS_DONT_REMOVE; + if(mi->IsSimple()) + ((CSimpleModelInfo*)mi)->m_alpha = 255; + } + } + + for(i = 0; i < TXDSTORESIZE; i++) + if(CTxdStore::GetSlot(i) && CTxdStore::GetSlot(i)->texDict) + ms_aInfoForModel[i + STREAM_OFFSET_TXD].m_loadState = STREAMSTATE_LOADED; + + + for(i = 0; i < MAXVEHICLESLOADED; i++) + ms_vehiclesLoaded[i] = -1; + ms_numVehiclesLoaded = 0; + + ms_pExtraObjectsDir = new CDirectory(EXTRADIRSIZE); + ms_numPriorityRequests = 0; + ms_hasLoadedLODs = true; + ms_currentPedGrp = -1; + ms_lastCullZone = -1; // unused because RemoveModelsNotVisibleFromCullzone is gone + ms_loadedGangs = 0; + ms_currentPedLoading = 8; // unused, whatever it is + + LoadCdDirectory(); + + // allocate streaming buffers + if(ms_streamingBufferSize & 1) ms_streamingBufferSize++; + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_streamingBufferSize /= 2; + ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; + debug("Streaming buffer size is %d sectors", ms_streamingBufferSize); + + // PC only, figure out how much memory we got +#ifdef GTA_PC +#define MB (1024*1024) + extern DWORD &_dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2; + if(ms_memoryAvailable < 50*MB) + ms_memoryAvailable = 50*MB; + desiredNumVehiclesLoaded = (ms_memoryAvailable/MB - 50)/3 + 12; + if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED) + desiredNumVehiclesLoaded = MAXVEHICLESLOADED; + debug("Memory allocated to Streaming is %dMB", ms_memoryAvailable/MB); +#undef MB +#endif + + // find island LODs + + pIslandLODindustEntity = nil; + pIslandLODcomIndEntity = nil; + pIslandLODcomSubEntity = nil; + pIslandLODsubIndEntity = nil; + pIslandLODsubComEntity = nil; + islandLODindust = -1; + islandLODcomInd = -1; + islandLODcomSub = -1; + islandLODsubInd = -1; + islandLODsubCom = -1; + CModelInfo::GetModelInfo("IslandLODInd", &islandLODindust); + CModelInfo::GetModelInfo("IslandLODcomIND", &islandLODcomInd); + CModelInfo::GetModelInfo("IslandLODcomSUB", &islandLODcomSub); + CModelInfo::GetModelInfo("IslandLODsubIND", &islandLODsubInd); + CModelInfo::GetModelInfo("IslandLODsubCOM", &islandLODsubCom); + + for(i = 0; i < CPools::GetBuildingPool()->GetSize(); i++){ + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if(building == nil) + continue; + if(building->GetModelIndex() == islandLODindust) pIslandLODindustEntity = building; + if(building->GetModelIndex() == islandLODcomInd) pIslandLODcomIndEntity = building; + if(building->GetModelIndex() == islandLODcomSub) pIslandLODcomSubEntity = building; + if(building->GetModelIndex() == islandLODsubInd) pIslandLODsubIndEntity = building; + if(building->GetModelIndex() == islandLODsubCom) pIslandLODsubComEntity = building; + } +} + +void +CStreaming::Shutdown(void) +{ + RwFreeAlign(ms_pStreamingBuffer[0]); + ms_streamingBufferSize = 0; + if(ms_pExtraObjectsDir) + delete ms_pExtraObjectsDir; +} + +void +CStreaming::Update(void) +{ + CEntity *train; + CStreamingInfo *si, *prev; + bool requestedSubway = false; + + UpdateMemoryUsed(); + + if(ms_channelError != -1){ + RetryLoadFile(ms_channelError); + return; + } + + if(CTimer::GetIsPaused()) + return; + + train = FindPlayerTrain(); + if(train && train->GetPosition().z < 0.0f){ + RequestSubway(); + requestedSubway = true; + }else if(!ms_disableStreaming) + AddModelsToRequestList(TheCamera.GetPosition()); + + DeleteFarAwayRwObjects(TheCamera.GetPosition()); + + if(!ms_disableStreaming && + !CCutsceneMgr::IsRunning() && + !requestedSubway && + !CGame::playingIntro && + ms_numModelsRequested < 5 && + !CRenderer::m_loadingPriority){ + StreamVehiclesAndPeds(); + StreamZoneModels(FindPlayerCoors()); + } + + LoadRequestedModels(); + + for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){ + prev = si->m_prev; + if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0) + RemoveModel(si - ms_aInfoForModel); + } +} + +void +CStreaming::LoadCdDirectory(void) +{ + char dirname[132]; + int i; + +#ifdef GTA_PC + ms_imageOffsets[0] = 0; + ms_imageOffsets[1] = -1; + ms_imageOffsets[2] = -1; + ms_imageOffsets[3] = -1; + ms_imageOffsets[4] = -1; + ms_imageOffsets[5] = -1; + ms_imageOffsets[6] = -1; + ms_imageOffsets[7] = -1; + ms_imageOffsets[8] = -1; + ms_imageOffsets[9] = -1; + ms_imageOffsets[10] = -1; + ms_imageOffsets[11] = -1; + ms_imageSize = GetGTA3ImgSize(); + // PS2 uses CFileMgr::GetCdFile on all IMG files to fill the array +#endif + + i = CdStreamGetNumImages(); + while(i-- >= 1){ + strcpy(dirname, CdStreamGetImageName(i)); + strncpy(strrchr(dirname, '.') + 1, "DIR", 3); + LoadCdDirectory(dirname, i); + } + + ms_lastImageRead = 0; + ms_imageSize /= CDSTREAM_SECTOR_SIZE; +} + +void +CStreaming::LoadCdDirectory(const char *dirname, int n) +{ + int fd, lastID, imgSelector; + int modelId, txdId; + uint32 posn, size; + CDirectory::DirectoryInfo direntry; + char *dot; + + lastID = -1; + fd = CFileMgr::OpenFile(dirname, "rb"); + assert(fd > 0); + + imgSelector = n<<24; + assert(sizeof(direntry) == 32); + while(CFileMgr::Read(fd, (char*)&direntry, sizeof(direntry))){ + dot = strchr(direntry.name, '.'); + if(dot) *dot = '\0'; + if(direntry.size > (uint32)ms_streamingBufferSize) + ms_streamingBufferSize = direntry.size; + + if(strcmp(dot+1, "DFF") == 0 || strcmp(dot+1, "dff") == 0){ + if(CModelInfo::GetModelInfo(direntry.name, &modelId)){ + if(ms_aInfoForModel[modelId].GetCdPosnAndSize(posn, size)){ + debug("%s appears more than once in %s\n", direntry.name, dirname); + lastID = -1; + }else{ + direntry.offset |= imgSelector; + ms_aInfoForModel[modelId].SetCdPosnAndSize(direntry.offset, direntry.size); + if(lastID != -1) + ms_aInfoForModel[lastID].m_nextID = modelId; + lastID = modelId; + } + }else{ + // BUG: doesn't remember which cdimage this was in + ms_pExtraObjectsDir->AddItem(direntry); + lastID = -1; + } + }else if(strcmp(dot+1, "TXD") == 0 || strcmp(dot+1, "txd") == 0){ + txdId = CTxdStore::FindTxdSlot(direntry.name); + if(txdId == -1) + txdId = CTxdStore::AddTxdSlot(direntry.name); + if(ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].GetCdPosnAndSize(posn, size)){ + debug("%s appears more than once in %s\n", direntry.name, dirname); + lastID = -1; + }else{ + direntry.offset |= imgSelector; + ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].SetCdPosnAndSize(direntry.offset, direntry.size); + if(lastID != -1) + ms_aInfoForModel[lastID].m_nextID = txdId + STREAM_OFFSET_TXD; + lastID = txdId + STREAM_OFFSET_TXD; + } + }else + lastID = -1; + } + + CFileMgr::CloseFile(fd); +} + +bool +CStreaming::ConvertBufferToObject(int8 *buf, int32 streamId) +{ + RwMemory mem; + RwStream *stream; + int cdsize; + uint32 startTime, endTime, timeDiff; + CBaseModelInfo *mi; + bool success; + + startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + + cdsize = ms_aInfoForModel[streamId].GetCdSize(); + mem.start = (uint8*)buf; + mem.length = cdsize * CDSTREAM_SECTOR_SIZE; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if(streamId < STREAM_OFFSET_TXD){ + // Model + mi = CModelInfo::GetModelInfo(streamId); + + // Txd has to be loaded + if(CTxdStore::GetSlot(mi->GetTxdSlot())->texDict == nil){ + debug("failed to load %s because TXD %s is not in memory\n", mi->GetName(), CTxdStore::GetTxdName(mi->GetTxdSlot())); + RemoveModel(streamId); + RemoveTxd(mi->GetTxdSlot()); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + // Set Txd to use + CTxdStore::AddRef(mi->GetTxdSlot()); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + + if(mi->IsSimple()){ + success = CFileLoader::LoadAtomicFile(stream, streamId); + }else if(mi->m_type == MITYPE_VEHICLE){ + // load vehicles in two parts + CModelInfo::GetModelInfo(streamId)->AddRef(); + success = CFileLoader::StartLoadClumpFile(stream, streamId); + if(success) + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED; + }else{ + success = CFileLoader::LoadClumpFile(stream, streamId); + } + UpdateMemoryUsed(); + + // Txd no longer needed unless we only read part of the file + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED) + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); + + if(!success){ + debug("Failed to load %s\n", CModelInfo::GetModelInfo(streamId)->GetName()); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + }else{ + // Txd + assert(streamId < NUMSTREAMINFO); + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY) == 0 && + !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){ + RemoveModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + if(ms_bLoadingBigModel || cdsize > 200){ + success = CTxdStore::StartLoadTxd(streamId - STREAM_OFFSET_TXD, stream); + if(success) + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED; + }else + success = CTxdStore::LoadTxd(streamId - STREAM_OFFSET_TXD, stream); + UpdateMemoryUsed(); + + if(!success){ + debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD)); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + } + + RwStreamClose(stream, &mem); + + // We shouldn't even end up here unless load was successful + if(!success){ + ReRequestModel(streamId); + if(streamId < STREAM_OFFSET_TXD) + debug("Failed to load %s.dff\n", mi->GetName()); + else + debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD)); + return false; + } + + if(streamId < STREAM_OFFSET_TXD){ + // Model + // Vehicles and Peds not in loaded list + if(mi->m_type != MITYPE_VEHICLE && mi->m_type != MITYPE_PED){ + CSimpleModelInfo *smi = (CSimpleModelInfo*)mi; + + // Set fading for some objects + if(mi->IsSimple() && !smi->m_isBigBuilding){ + if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOFADE) + smi->m_alpha = 255; + else + smi->m_alpha = 0; + } + + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + }else{ + // Txd + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + + // Mark objects as loaded + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + } + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5){ + if(streamId < STREAM_OFFSET_TXD) + debug("model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetName(), timeDiff); + else + debug("txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff); + } + + return true; +} + + +bool +CStreaming::FinishLoadingLargeFile(int8 *buf, int32 streamId) +{ + RwMemory mem; + RwStream *stream; + uint32 startTime, endTime, timeDiff; + CBaseModelInfo *mi; + bool success; + + startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ + if(streamId < STREAM_OFFSET_TXD) + CModelInfo::GetModelInfo(streamId)->RemoveRef(); + return false; + } + + mem.start = (uint8*)buf; + mem.length = ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if(streamId < STREAM_OFFSET_TXD){ + // Model + mi = CModelInfo::GetModelInfo(streamId); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + success = CFileLoader::FinishLoadClumpFile(stream, streamId); + if(success) + success = AddToLoadedVehiclesList(streamId); + mi->RemoveRef(); + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); + }else{ + // Txd + CTxdStore::AddRef(streamId - STREAM_OFFSET_TXD); + success = CTxdStore::FinishLoadTxd(streamId - STREAM_OFFSET_TXD, stream); + CTxdStore::RemoveRefWithoutDelete(streamId - STREAM_OFFSET_TXD); + } + + RwStreamClose(stream, &mem); + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + + if(!success){ + RemoveModel(streamId); + ReRequestModel(streamId); + UpdateMemoryUsed(); + return false; + } + + UpdateMemoryUsed(); + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5){ + if(streamId < STREAM_OFFSET_TXD) + debug("finish model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetName(), timeDiff); + else + debug("finish txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff); + } + + return true; +} + +void +CStreaming::RequestModel(int32 id, int32 flags) +{ + CSimpleModelInfo *mi; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE){ + // updgrade to priority + if(flags & STREAMFLAGS_PRIORITY && !ms_aInfoForModel[id].IsPriority()){ + ms_numPriorityRequests++; + ms_aInfoForModel[id].m_flags |= STREAMFLAGS_PRIORITY; + } + }else if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_NOTLOADED){ + flags &= ~STREAMFLAGS_PRIORITY; + } + ms_aInfoForModel[id].m_flags |= flags; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ + // Already loaded, only check changed flags + + if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOFADE && id < STREAM_OFFSET_TXD){ + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + + // reinsert into list + if(ms_aInfoForModel[id].m_next){ + ms_aInfoForModel[id].RemoveFromList(); + if((ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } + }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED || + ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ // how can this be true again? + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED){ + if(id < STREAM_OFFSET_TXD) + RequestTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot(), flags); + ms_aInfoForModel[id].AddToList(&ms_startRequestedList); + ms_numModelsRequested++; + if(flags & STREAMFLAGS_PRIORITY) + ms_numPriorityRequests++; + } + + ms_aInfoForModel[id].m_loadState = STREAMSTATE_INQUEUE; + ms_aInfoForModel[id].m_flags = flags; + } +} + +void +CStreaming::RequestSubway(void) +{ + RequestModel(MI_SUBWAY1, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY2, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY3, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY4, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY5, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY6, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY7, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY8, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY9, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY10, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY11, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY12, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY13, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY14, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY15, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY16, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY17, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY18, STREAMFLAGS_NOFADE); + + switch(CGame::currLevel){ + case LEVEL_INDUSTRIAL: + RequestModel(MI_SUBPLATFORM_IND, STREAMFLAGS_NOFADE); + break; + case LEVEL_COMMERCIAL: + if(FindPlayerTrain()->GetPosition().y < -700.0f){ + RequestModel(MI_SUBPLATFORM_COMS, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBPLATFORM_COMS2, STREAMFLAGS_NOFADE); + }else{ + RequestModel(MI_SUBPLATFORM_COMN, STREAMFLAGS_NOFADE); + } + break; + case LEVEL_SUBURBAN: + RequestModel(MI_SUBPLATFORM_SUB, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBPLATFORM_SUB2, STREAMFLAGS_NOFADE); + break; + } +} + +void +CStreaming::RequestBigBuildings(eLevelName level) +{ + int i, n; + CBuilding *b; + + n = CPools::GetBuildingPool()->GetSize(); + for(i = 0; i < n; i++){ + b = CPools::GetBuildingPool()->GetSlot(i); + if(b && b->bIsBIGBuilding && b->m_level == level) + RequestModel(b->GetModelIndex(), STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + } + RequestIslands(level); + ms_hasLoadedLODs = false; +} + +void +CStreaming::RequestIslands(eLevelName level) +{ + switch(level){ + case LEVEL_INDUSTRIAL: + RequestModel(islandLODcomInd, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + RequestModel(islandLODsubInd, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + break; + case LEVEL_COMMERCIAL: + RequestModel(islandLODindust, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + RequestModel(islandLODsubCom, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + break; + case LEVEL_SUBURBAN: + RequestModel(islandLODindust, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + RequestModel(islandLODcomSub, STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY); + break; + } +} + +void +CStreaming::RequestSpecialModel(int32 modelId, const char *modelName, int32 flags) +{ + CBaseModelInfo *mi; + int txdId; + char oldName[48]; + uint32 pos, size; + + mi = CModelInfo::GetModelInfo(modelId); + if(strcmp(mi->GetName(), modelName) == 0){ + // Already have the correct name, just request it + RequestModel(modelId, flags); + return; + } + + strcpy(oldName, mi->GetName()); + mi->SetName(modelName); + + // What exactly is going on here? + if(CModelInfo::GetModelInfo(oldName, nil)){ + txdId = CTxdStore::FindTxdSlot(oldName); + if(txdId != -1 && CTxdStore::GetSlot(txdId)->texDict){ + CTxdStore::AddRef(txdId); + RemoveModel(modelId); + CTxdStore::RemoveRefWithoutDelete(txdId); + }else + RemoveModel(modelId); + }else + RemoveModel(modelId); + + ms_pExtraObjectsDir->FindItem(modelName, pos, size); + mi->ClearTexDictionary(); + if(CTxdStore::FindTxdSlot(modelName) == -1) + mi->SetTexDictionary("generic"); + else + mi->SetTexDictionary(modelName); + ms_aInfoForModel[modelId].SetCdPosnAndSize(pos, size); + RequestModel(modelId, flags); +} + +void +CStreaming::RequestSpecialChar(int32 charId, const char *modelName, int32 flags) +{ + RequestSpecialModel(charId + MI_SPECIAL01, modelName, flags); +} + +bool +CStreaming::HasSpecialCharLoaded(int32 id) +{ + return HasModelLoaded(id + MI_SPECIAL01); +} + +void +CStreaming::SetMissionDoesntRequireSpecialChar(int32 id) +{ + return SetMissionDoesntRequireModel(id + MI_SPECIAL01); +} + +void +CStreaming::DecrementRef(int32 id) +{ + ms_numModelsRequested--; + if(ms_aInfoForModel[id].IsPriority()){ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_PRIORITY; + ms_numPriorityRequests--; + } +} + +void +CStreaming::RemoveModel(int32 id) +{ + int i; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED) + return; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ + if(id < STREAM_OFFSET_TXD) + CModelInfo::GetModelInfo(id)->DeleteRwObject(); + else + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); + ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; + } + + if(ms_aInfoForModel[id].m_next){ + // Remove from list, model is neither loaded nor requested + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE) + DecrementRef(id); + ms_aInfoForModel[id].RemoveFromList(); + }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_READING){ + for(i = 0; i < 4; i++){ + if(ms_channel[0].streamIds[i] == id) + ms_channel[0].streamIds[i] = -1; + if(ms_channel[1].streamIds[i] == id) + ms_channel[1].streamIds[i] = -1; + } + } + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){ + if(id < STREAM_OFFSET_TXD) + RpClumpGtaCancelStream(); + else + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); + } + + ms_aInfoForModel[id].m_loadState = STREAMSTATE_NOTLOADED; +} + +void +CStreaming::RemoveUnusedBuildings(eLevelName level) +{ + if(level != LEVEL_INDUSTRIAL) + RemoveBuildings(LEVEL_INDUSTRIAL); + if(level != LEVEL_COMMERCIAL) + RemoveBuildings(LEVEL_COMMERCIAL); + if(level != LEVEL_SUBURBAN) + RemoveBuildings(LEVEL_SUBURBAN); +} + +void +CStreaming::RemoveBuildings(eLevelName level) +{ + int i, n; + CEntity *e; + CBaseModelInfo *mi; + + n = CPools::GetBuildingPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if(mi->m_refCount == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetTreadablePool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if(mi->m_refCount == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetObjectPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetObjectPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered && ((CObject*)e)->ObjectCreatedBy == GAME_OBJECT){ + e->DeleteRwObject(); + if(mi->m_refCount == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetDummyPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetDummyPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if(mi->m_refCount == 0) + RemoveModel(e->GetModelIndex()); + } + } + } +} + +void +CStreaming::RemoveUnusedBigBuildings(eLevelName level) +{ + if(level != LEVEL_INDUSTRIAL) + RemoveBigBuildings(LEVEL_INDUSTRIAL); + if(level != LEVEL_COMMERCIAL) + RemoveBigBuildings(LEVEL_COMMERCIAL); + if(level != LEVEL_SUBURBAN) + RemoveBigBuildings(LEVEL_SUBURBAN); + RemoveIslandsNotUsed(level); +} + +void +DeleteIsland(CEntity *island) +{ + if(island == nil) + return; + if(island->bImBeingRendered) + debug("Didn't delete island because it was being rendered\n"); + else{ + island->DeleteRwObject(); + CStreaming::RemoveModel(island->GetModelIndex()); + } +} + +void +CStreaming::RemoveIslandsNotUsed(eLevelName level) +{ + switch(level){ + case LEVEL_INDUSTRIAL: + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubComEntity); + break; + case LEVEL_COMMERCIAL: + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + break; + case LEVEL_SUBURBAN: + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + DeleteIsland(pIslandLODcomIndEntity); + break; + default: + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + break; + } +} + +void +CStreaming::RemoveBigBuildings(eLevelName level) +{ + int i, n; + CEntity *e; + CBaseModelInfo *mi; + + n = CPools::GetBuildingPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsBIGBuilding && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if(mi->m_refCount == 0) + RemoveModel(e->GetModelIndex()); + } + } + } +} + +bool +CStreaming::RemoveLoadedVehicle(void) +{ + int i, id; + + for(i = 0; i < MAXVEHICLESLOADED; i++){ + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + id = ms_vehiclesLoaded[ms_lastVehicleDeleted]; + if(id != -1 && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 && + CModelInfo::GetModelInfo(id)->m_refCount == 0 && + ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED) + goto found; + } + return false; +found: + RemoveModel(ms_vehiclesLoaded[ms_lastVehicleDeleted]); + ms_numVehiclesLoaded--; + ms_vehiclesLoaded[ms_lastVehicleDeleted] = -1; + return true; +} + +bool +CStreaming::RemoveLeastUsedModel(void) +{ + CStreamingInfo *si; + int streamId; + + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + streamId = si - ms_aInfoForModel; + if(streamId < STREAM_OFFSET_TXD){ + if(CModelInfo::GetModelInfo(streamId)->m_refCount == 0){ + RemoveModel(streamId); + return true; + } + }else{ + if(CTxdStore::GetNumRefs(streamId - STREAM_OFFSET_TXD) == 0 && + !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){ + RemoveModel(streamId); + return true; + } + } + } + return ms_numVehiclesLoaded > 7 && RemoveLoadedVehicle(); +} + +void +CStreaming::RemoveAllUnusedModels(void) +{ + int i; + + for(i = 0; i < MAXVEHICLESLOADED; i++) + RemoveLoadedVehicle(); + + for(i = NUM_DEFAULT_MODELS; i < MODELINFOSIZE; i++){ + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED && + ms_aInfoForModel[i].m_flags & STREAMFLAGS_DONT_REMOVE && + CModelInfo::GetModelInfo(i)->m_refCount == 0){ + RemoveModel(i); + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + } + } +} + +bool +CStreaming::RemoveReferencedTxds(int32 mem) +{ + CStreamingInfo *si; + int streamId; + + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + streamId = si - ms_aInfoForModel; + if(streamId >= STREAM_OFFSET_TXD && + CTxdStore::GetNumRefs(streamId-STREAM_OFFSET_TXD) == 0){ + RemoveModel(streamId); + if(ms_memoryUsed < mem) + return true; + } + } + return false; +} + +// TODO: RemoveCurrentZonesModels + +void +CStreaming::RemoveUnusedModelsInLoadedList(void) +{ + // empty +} + +bool +CStreaming::IsTxdUsedByRequestedModels(int32 txdId) +{ + CStreamingInfo *si; + int streamId; + int i; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = si->m_next){ + streamId = si - ms_aInfoForModel; + if(streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + } + + for(i = 0; i < 4; i++){ + streamId = ms_channel[0].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + streamId = ms_channel[1].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + } + + return false; +} + +int32 +CStreaming::GetAvailableVehicleSlot(void) +{ + int i; + for(i = 0; i < MAXVEHICLESLOADED; i++) + if(ms_vehiclesLoaded[i] == -1) + return i; + return -1; +} + +bool +CStreaming::AddToLoadedVehiclesList(int32 modelId) +{ + int i; + int id; + + if(ms_numVehiclesLoaded < desiredNumVehiclesLoaded){ + // still room for vehicles + for(i = 0; i < MAXVEHICLESLOADED; i++){ + if(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1) + break; + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + } + assert(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1); + ms_numVehiclesLoaded++; + }else{ + // find vehicle we can remove + for(i = 0; i < MAXVEHICLESLOADED; i++){ + id = ms_vehiclesLoaded[ms_lastVehicleDeleted]; + if(id != -1 && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 && + CModelInfo::GetModelInfo(id)->m_refCount == 0) + goto found; + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + } + id = -1; +found: + if(id == -1){ + // didn't find anything, try a free slot + id = GetAvailableVehicleSlot(); + if(id == -1) + return false; // still no luck + ms_lastVehicleDeleted = id; + // this is more that we wanted actually + ms_numVehiclesLoaded++; + }else + RemoveModel(id); + } + + ms_vehiclesLoaded[ms_lastVehicleDeleted++] = modelId; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + return true; +} + +bool +CStreaming::IsObjectInCdImage(int32 id) +{ + uint32 posn, size; + return ms_aInfoForModel[id].GetCdPosnAndSize(posn, size); +} + +void +CStreaming::HaveAllBigBuildingsLoaded(eLevelName level) +{ + int i, n; + CEntity *e; + + if(ms_hasLoadedLODs) + return; + + if(level == LEVEL_INDUSTRIAL){ + if(ms_aInfoForModel[islandLODcomInd].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODsubInd].m_loadState != STREAMSTATE_LOADED) + return; + }else if(level == LEVEL_COMMERCIAL){ + if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODsubCom].m_loadState != STREAMSTATE_LOADED) + return; + }else if(level == LEVEL_SUBURBAN){ + if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODcomSub].m_loadState != STREAMSTATE_LOADED) + return; + } + + n = CPools::GetBuildingPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsBIGBuilding && e->m_level == level && + ms_aInfoForModel[e->GetModelIndex()].m_loadState != STREAMSTATE_LOADED) + return; + } + + RemoveUnusedBigBuildings(level); + ms_hasLoadedLODs = true; +} + +void +CStreaming::SetModelIsDeletable(int32 id) +{ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_DONT_REMOVE; + if((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->m_type != MITYPE_VEHICLE) && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_SCRIPTOWNED) == 0){ + if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED) + RemoveModel(id); + else if(ms_aInfoForModel[id].m_next == nil) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } +} + +void +CStreaming::SetModelTxdIsDeletable(int32 id) +{ + SetModelIsDeletable(CModelInfo::GetModelInfo(id)->GetTxdSlot() + STREAM_OFFSET_TXD); +} + +void +CStreaming::SetMissionDoesntRequireModel(int32 id) +{ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_SCRIPTOWNED; + if((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->m_type != MITYPE_VEHICLE) && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_DONT_REMOVE) == 0){ + if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED) + RemoveModel(id); + else if(ms_aInfoForModel[id].m_next == nil) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } +} + +void +CStreaming::LoadInitialPeds(void) +{ + RequestModel(MI_COP, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_TAXI_D, STREAMFLAGS_DONT_REMOVE); +} + +void +CStreaming::LoadInitialVehicles(void) +{ + int id; + + ms_numVehiclesLoaded = 0; + ms_lastVehicleDeleted = 0; + + if(CModelInfo::GetModelInfo("taxi", &id)) + RequestModel(id, STREAMFLAGS_DONT_REMOVE); + if(CModelInfo::GetModelInfo("police", &id)) + RequestModel(id, STREAMFLAGS_DONT_REMOVE); +} + +void +CStreaming::StreamVehiclesAndPeds(void) +{ + int i, model; + static int timeBeforeNextLoad = 0; + static int modelQualityClass = 0; + + if(CRecordDataForGame::RecordingState == RECORDSTATE_1 || + CRecordDataForGame::RecordingState == RECORDSTATE_2) + return; + + if(FindPlayerPed()->m_pWanted->AreSwatRequired()){ + RequestModel(MI_ENFORCER, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_SWAT, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_ENFORCER); + if(!HasModelLoaded(MI_ENFORCER)) + SetModelIsDeletable(MI_SWAT); + } + + if(FindPlayerPed()->m_pWanted->AreFbiRequired()){ + RequestModel(MI_FBICAR, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_FBI, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_FBICAR); + if(!HasModelLoaded(MI_FBICAR)) + SetModelIsDeletable(MI_FBI); + } + + if(FindPlayerPed()->m_pWanted->AreArmyRequired()){ + RequestModel(MI_RHINO, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_BARRACKS, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_ARMY, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_RHINO); + SetModelIsDeletable(MI_BARRACKS); + if(!HasModelLoaded(MI_RHINO) && !HasModelLoaded(MI_BARRACKS)) + SetModelIsDeletable(MI_ARMY); + } + + if(FindPlayerPed()->m_pWanted->NumOfHelisRequired() > 0) + RequestModel(MI_CHOPPER, STREAMFLAGS_DONT_REMOVE); + else + SetModelIsDeletable(MI_CHOPPER); + + if(timeBeforeNextLoad >= 0) + timeBeforeNextLoad--; + else if(ms_numVehiclesLoaded <= desiredNumVehiclesLoaded){ + for(i = 0; i <= 10; i++){ + model = CCarCtrl::ChooseCarModel(modelQualityClass); + modelQualityClass++; + if(modelQualityClass >= NUM_VEHICLE_CLASSES) + modelQualityClass = 0; + + // check if we want to load this model + if(ms_aInfoForModel[model].m_loadState == STREAMSTATE_NOTLOADED && + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(model))->m_level & (1 << (CGame::currLevel-1))) + break; + } + + if(i <= 10){ + RequestModel(model, STREAMFLAGS_DEPENDENCY); + timeBeforeNextLoad = 500; + } + } +} + +void +CStreaming::StreamZoneModels(const CVector &pos) +{ + int i; + uint16 gangsToLoad, gangCarsToLoad, bit; + CZoneInfo info; + + CTheZones::GetZoneInfoForTimeOfDay(&pos, &info); + + if(info.pedGroup != ms_currentPedGrp){ + + // unload pevious group + if(ms_currentPedGrp != -1) + for(i = 0; i < 8; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1) + break; + SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + SetModelTxdIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + } + + ms_currentPedGrp = info.pedGroup; + + for(i = 0; i < 8; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1) + break; + RequestModel(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i], STREAMFLAGS_DONT_REMOVE); + } + } + RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE); + + gangsToLoad = 0; + gangCarsToLoad = 0; + if(info.gangDensity[0] != 0) gangsToLoad |= 1<<0; + if(info.gangDensity[1] != 0) gangsToLoad |= 1<<1; + if(info.gangDensity[2] != 0) gangsToLoad |= 1<<2; + if(info.gangDensity[3] != 0) gangsToLoad |= 1<<3; + if(info.gangDensity[4] != 0) gangsToLoad |= 1<<4; + if(info.gangDensity[5] != 0) gangsToLoad |= 1<<5; + if(info.gangDensity[6] != 0) gangsToLoad |= 1<<6; + if(info.gangDensity[7] != 0) gangsToLoad |= 1<<7; + if(info.gangDensity[8] != 0) gangsToLoad |= 1<<8; + if(info.gangThreshold[0] != info.copDensity) gangCarsToLoad |= 1<<0; + if(info.gangThreshold[1] != info.gangThreshold[0]) gangCarsToLoad |= 1<<1; + if(info.gangThreshold[2] != info.gangThreshold[1]) gangCarsToLoad |= 1<<2; + if(info.gangThreshold[3] != info.gangThreshold[2]) gangCarsToLoad |= 1<<3; + if(info.gangThreshold[4] != info.gangThreshold[3]) gangCarsToLoad |= 1<<4; + if(info.gangThreshold[5] != info.gangThreshold[4]) gangCarsToLoad |= 1<<5; + if(info.gangThreshold[6] != info.gangThreshold[5]) gangCarsToLoad |= 1<<6; + if(info.gangThreshold[7] != info.gangThreshold[6]) gangCarsToLoad |= 1<<7; + if(info.gangThreshold[8] != info.gangThreshold[7]) gangCarsToLoad |= 1<<8; + + if(gangsToLoad == ms_loadedGangs && gangCarsToLoad == ms_loadedGangCars) + return; + + // This makes things simpler than the game does it + gangsToLoad |= gangCarsToLoad; + + for(i = 0; i < NUM_GANGS; i++){ + bit = 1<<i; + + if(gangsToLoad & bit && (ms_loadedGangs & bit) == 0){ + RequestModel(MI_GANG01 + i*2, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_GANG01 + i*2 + 1, STREAMFLAGS_DONT_REMOVE); + ms_loadedGangs |= bit; + }else if((gangsToLoad & bit) == 0 && ms_loadedGangs & bit){ + SetModelIsDeletable(MI_GANG01 + i*2); + SetModelIsDeletable(MI_GANG01 + i*2 + 1); + SetModelTxdIsDeletable(MI_GANG01 + i*2); + SetModelTxdIsDeletable(MI_GANG01 + i*2 + 1); + ms_loadedGangs &= ~bit; + } + + if(gangCarsToLoad & bit && (ms_loadedGangCars & bit) == 0){ + RequestModel(CGangs::GetGangInfo(i)->m_nVehicleMI, STREAMFLAGS_DONT_REMOVE); + }else if((gangCarsToLoad & bit) == 0 && ms_loadedGangCars & bit){ + SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + SetModelTxdIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + } + } + ms_loadedGangCars = gangCarsToLoad; +} + +void +CStreaming::RemoveCurrentZonesModels(void) +{ + int i; + + if(ms_currentPedGrp != -1) + for(i = 0; i < 8; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1) + break; + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != MI_MALE01) + SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + } + + for(i = 0; i < NUM_GANGS; i++){ + SetModelIsDeletable(MI_GANG01 + i*2); + SetModelIsDeletable(MI_GANG01 + i*2 + 1); + if(CGangs::GetGangInfo(i)->m_nVehicleMI != -1) + SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + } + + ms_currentPedGrp = -1; + ms_loadedGangs = 0; + ms_loadedGangCars = 0; +} + + + +// Find starting offset of the cdimage we next want to read +// Not useful at all on PC... +int32 +CStreaming::GetCdImageOffset(int32 lastPosn) +{ + int offset, off; + int i, img; + int dist, mindist; + + img = -1; + mindist = INT_MAX; + offset = ms_imageOffsets[ms_lastImageRead]; + if(lastPosn <= offset || lastPosn > offset + ms_imageSize){ + // last read position is not in last image + for(i = 0; i < NUMCDIMAGES; i++){ + off = ms_imageOffsets[i]; + if(off == -1) continue; + if((uint32)lastPosn > (uint32)off) + // after start of image, get distance from end + // negative if before end! + dist = lastPosn - (off + ms_imageSize); + else + // before image, get offset to start + // this will never be negative + dist = off - lastPosn; + if(dist < mindist){ + img = i; + mindist = dist; + } + } + assert(img >= 0); + offset = ms_imageOffsets[img]; + ms_lastImageRead = img; + } + return offset; +} + +inline bool +TxdAvailable(int32 txdId) +{ + CStreamingInfo *si = &CStreaming::ms_aInfoForModel[txdId + STREAM_OFFSET_TXD]; + return si->m_loadState == STREAMSTATE_LOADED || si->m_loadState == STREAMSTATE_READING; +} + +// Find stream id of next requested file in cdimage +int32 +CStreaming::GetNextFileOnCd(int32 lastPosn, bool priority) +{ + CStreamingInfo *si, *next; + int streamId; + uint32 posn, size; + int streamIdFirst, streamIdNext; + uint32 posnFirst, posnNext; + + streamIdFirst = -1; + streamIdNext = -1; + posnFirst = UINT_MAX; + posnNext = UINT_MAX; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){ + next = si->m_next; + streamId = si - ms_aInfoForModel; + + // only priority requests if there are any + if(priority && ms_numPriorityRequests != 0 && !si->IsPriority()) + continue; + + // request Txd if necessary + if(streamId < STREAM_OFFSET_TXD && + !TxdAvailable(CModelInfo::GetModelInfo(streamId)->GetTxdSlot())){ + ReRequestTxd(CModelInfo::GetModelInfo(streamId)->GetTxdSlot()); + }else if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){ + if(posn < posnFirst){ + // find first requested file in image + streamIdFirst = streamId; + posnFirst = posn; + } + if(posn < posnNext && posn >= (uint32)lastPosn){ + // find first requested file after last read position + streamIdNext = streamId; + posnNext = posn; + } + }else{ + // empty file + DecrementRef(streamId); + si->RemoveFromList(); + si->m_loadState = STREAMSTATE_LOADED; + } + } + + // wrap around + if(streamIdNext == -1) + streamIdNext = streamIdFirst; + + if(streamIdNext == -1 && ms_numPriorityRequests != 0){ + // try non-priority files + ms_numPriorityRequests = 0; + streamIdNext = GetNextFileOnCd(lastPosn, false); + } + + return streamIdNext; +} + +/* + * Streaming buffer size is half of the largest file. + * Files larger than the buffer size can only be loaded by channel 0, + * which then uses both buffers, while channel 1 is idle. + * ms_bLoadingBigModel is set to true to indicate this state. + * + * TODO: two-part files + */ + +// Make channel read from disc +void +CStreaming::RequestModelStream(int32 ch) +{ + int lastPosn, imgOffset, streamId; + int totalSize; + uint32 posn, size, unused; + int i; + int haveBigFile, havePed; + + lastPosn = CdStreamGetLastPosn(); + imgOffset = GetCdImageOffset(lastPosn); + streamId = GetNextFileOnCd(lastPosn - imgOffset, true); + + if(streamId == -1) + return; + + // remove Txds that aren't requested anymore + while(streamId >= STREAM_OFFSET_TXD){ + if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY || + IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)) + break; + RemoveModel(streamId); + // so try next file + ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size); + streamId = GetNextFileOnCd(posn + size, true); + } + + if(streamId == -1) + return; + + ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size); + if(size > (uint32)ms_streamingBufferSize){ + // Can only load big models on channel 0, and 1 has to be idle + if(ch == 1 || ms_channel[1].state != CHANNELSTATE_IDLE) + return; + ms_bLoadingBigModel = true; + } + + // Load up to 4 adjacent files + haveBigFile = 0; + havePed = 0; + totalSize = 0; + for(i = 0; i < 4; i++){ + // no more files we can read + if(streamId == -1 || ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_INQUEUE) + break; + + // also stop at non-priority files + ms_aInfoForModel[streamId].GetCdPosnAndSize(unused, size); + if(ms_numPriorityRequests != 0 && !ms_aInfoForModel[streamId].IsPriority()) + break; + + // Can't load certain combinations of files together + if(streamId < STREAM_OFFSET_TXD){ + if(havePed && CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_PED || + haveBigFile && CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_VEHICLE || + !TxdAvailable(CModelInfo::GetModelInfo(streamId)->GetTxdSlot())) + break; + }else{ + if(haveBigFile && size > 200) + break; + } + + // Now add the file + ms_channel[ch].streamIds[i] = streamId; + ms_channel[ch].offsets[i] = totalSize; + totalSize += size; + + // To big for buffer, remove again + if(totalSize > ms_streamingBufferSize && i > 0){ + totalSize -= size; + break; + } + if(streamId < STREAM_OFFSET_TXD){ + if(CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_PED) + havePed = 1; + if(CModelInfo::GetModelInfo(streamId)->m_type == MITYPE_VEHICLE) + haveBigFile = 1; + }else{ + if(size > 200) + haveBigFile = 1; + } + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING; + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + streamId = ms_aInfoForModel[streamId].m_nextID; + } + + // clear remaining slots + for(; i < 4; i++) + ms_channel[ch].streamIds[i] = -1; + // Now read the data + assert(!(ms_bLoadingBigModel && ch == 1)); // this would clobber the buffer + if(CdStreamRead(ch, ms_pStreamingBuffer[ch], imgOffset+posn, totalSize) == STREAM_NONE) + debug("FUCKFUCKFUCK\n"); + ms_channel[ch].state = CHANNELSTATE_READING; + ms_channel[ch].field24 = 0; + ms_channel[ch].size = totalSize; + ms_channel[ch].position = imgOffset+posn; + ms_channel[ch].numTries = 0; +} + +// Load data previously read from disc +bool +CStreaming::ProcessLoadingChannel(int32 ch) +{ + int status; + int i, id, cdsize; + + status = CdStreamGetStatus(ch); + if(status != STREAM_NONE){ + // busy + if(status != STREAM_READING && status != STREAM_WAITING){ + ms_channelError = ch; + ms_channel[ch].state = CHANNELSTATE_ERROR; + ms_channel[ch].status = status; + } + return false; + } + + if(ms_channel[ch].state == CHANNELSTATE_STARTED){ + ms_channel[ch].state = CHANNELSTATE_IDLE; + FinishLoadingLargeFile(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[0]*CDSTREAM_SECTOR_SIZE], + ms_channel[ch].streamIds[0]); + ms_channel[ch].streamIds[0] = -1; + }else{ + ms_channel[ch].state = CHANNELSTATE_IDLE; + for(i = 0; i < 4; i++){ + id = ms_channel[ch].streamIds[i]; + if(id == -1) + continue; + + cdsize = ms_aInfoForModel[id].GetCdSize(); + if(id < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(id)->m_type == MITYPE_VEHICLE && + ms_numVehiclesLoaded >= desiredNumVehiclesLoaded && + !RemoveLoadedVehicle() && + ((ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) == 0 || GetAvailableVehicleSlot() == -1)){ + // can't load vehicle + RemoveModel(id); + if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOT_IN_LIST) + ReRequestModel(id); + else if(CTxdStore::GetNumRefs(CModelInfo::GetModelInfo(id)->GetTxdSlot()) == 0) + RemoveTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot()); + }else{ + MakeSpaceFor(cdsize * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[i]*CDSTREAM_SECTOR_SIZE], + id); + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){ + // queue for second part + ms_channel[ch].state = CHANNELSTATE_STARTED; + ms_channel[ch].offsets[0] = ms_channel[ch].offsets[i]; + ms_channel[ch].streamIds[0] = id; + if(i != 0) + ms_channel[ch].streamIds[i] = -1; + }else + ms_channel[ch].streamIds[i] = -1; + } + } + } + + if(ms_bLoadingBigModel && ms_channel[ch].state != CHANNELSTATE_STARTED){ + ms_bLoadingBigModel = false; + // reset channel 1 after loading a big model + for(i = 0; i < 4; i++) + ms_channel[1].streamIds[i] = -1; + ms_channel[1].state = CHANNELSTATE_IDLE; + } + + return true; +} + +void +CStreaming::RetryLoadFile(int32 ch) +{ + char *key; + + CPad::StopPadsShaking(); + + if(ms_channel[ch].numTries >= 3){ + switch(ms_channel[ch].status){ + case STREAM_ERROR_NOCD: key = "NOCD"; break; + case STREAM_ERROR_OPENCD: key = "OPENCD"; break; + case STREAM_ERROR_WRONGCD: key = "WRONGCD"; break; + default: key = "CDERROR"; break; + } + CHud::SetMessage(TheText.Get(key)); + CTimer::SetCodePause(true); + } + + switch(ms_channel[ch].state){ + case CHANNELSTATE_IDLE: +streamread: + CdStreamRead(ch, ms_pStreamingBuffer[ch], ms_channel[ch].position, ms_channel[ch].size); + ms_channel[ch].state = CHANNELSTATE_READING; + ms_channel[ch].field24 = -600; + break; + case CHANNELSTATE_READING: + if(ProcessLoadingChannel(ch)){ + ms_channelError = -1; + CTimer::SetCodePause(false); + } + break; + case CHANNELSTATE_ERROR: + ms_channel[ch].numTries++; + if(CdStreamGetStatus(ch) != STREAM_READING && CdStreamGetStatus(ch) != STREAM_WAITING) + goto streamread; + break; + } +} + +void +CStreaming::LoadRequestedModels(void) +{ + static int currentChannel = 0; + + // We can't read with channel 1 while channel 0 is using its buffer + if(ms_bLoadingBigModel) + currentChannel = 0; + + // We have data, load + if(ms_channel[currentChannel].state == CHANNELSTATE_READING || + ms_channel[currentChannel].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(currentChannel); + + if(ms_channelError == -1){ + // Channel is idle, read more data + if(ms_channel[currentChannel].state == CHANNELSTATE_IDLE) + RequestModelStream(currentChannel); + // Switch channel + if(ms_channel[currentChannel].state != CHANNELSTATE_STARTED) + currentChannel = 1 - currentChannel; + } +} + +void +CStreaming::LoadAllRequestedModels(bool priority) +{ + static bool bInsideLoadAll = false; + int imgOffset, streamId, status; + int i; + uint32 posn, size; + + if(bInsideLoadAll) + return; + + FlushChannels(); + imgOffset = GetCdImageOffset(CdStreamGetLastPosn()); + + while(ms_endRequestedList.m_prev != &ms_startRequestedList){ + streamId = GetNextFileOnCd(0, priority); + if(streamId == -1) + break; + + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){ + do + status = CdStreamRead(0, ms_pStreamingBuffer[0], imgOffset+posn, size); + while(CdStreamSync(0) || status == STREAM_NONE); + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING; + + MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(ms_pStreamingBuffer[0], streamId); + if(ms_aInfoForModel[streamId].m_loadState == STREAMSTATE_STARTED) + FinishLoadingLargeFile(ms_pStreamingBuffer[0], streamId); + + if(streamId < STREAM_OFFSET_TXD){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(streamId); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + }else{ + // empty + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + } + } + + ms_bLoadingBigModel = false; + for(i = 0; i < 4; i++){ + ms_channel[1].streamIds[i] = -1; + ms_channel[1].offsets[i] = -1; + } + ms_channel[1].state = CHANNELSTATE_IDLE; + bInsideLoadAll = false; +} + +void +CStreaming::FlushChannels(void) +{ + if(ms_channel[1].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(1); + + if(ms_channel[0].state == CHANNELSTATE_READING){ + CdStreamSync(0); + ProcessLoadingChannel(0); + } + if(ms_channel[0].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(0); + + if(ms_channel[1].state == CHANNELSTATE_READING){ + CdStreamSync(1); + ProcessLoadingChannel(1); + } + if(ms_channel[1].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(1); +} + +void +CStreaming::FlushRequestList(void) +{ + CStreamingInfo *si, *next; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){ + next = si->m_next; + RemoveModel(si - ms_aInfoForModel); + } + FlushChannels(); +} + + +void +CStreaming::ImGonnaUseStreamingMemory(void) +{ + // empty +} + +void +CStreaming::IHaveUsedStreamingMemory(void) +{ + UpdateMemoryUsed(); +} + +void +CStreaming::UpdateMemoryUsed(void) +{ + // empty +} + +#define STREAM_DIST (2*SECTOR_SIZE_X) + +void +CStreaming::AddModelsToRequestList(const CVector &pos) +{ + float xmin, xmax, ymin, ymax; + int ixmin, ixmax, iymin, iymax; + int ix, iy; + int dx, dy, d; + CSector *sect; + + xmin = pos.x - STREAM_DIST; + ymin = pos.y - STREAM_DIST; + xmax = pos.x + STREAM_DIST; + ymax = pos.y + STREAM_DIST; + + ixmin = CWorld::GetSectorIndexX(xmin); + if(ixmin < 0) ixmin = 0; + ixmax = CWorld::GetSectorIndexX(xmax); + if(ixmax >= NUMSECTORS_X) ixmax = NUMSECTORS_X-1; + iymin = CWorld::GetSectorIndexY(ymin); + if(iymin < 0) iymin = 0; + iymax = CWorld::GetSectorIndexY(ymax); + if(iymax >= NUMSECTORS_Y) iymax = NUMSECTORS_Y-1; + + CWorld::AdvanceCurrentScanCode(); + + for(iy = iymin; iy < iymax; iy++){ + dy = iy - CWorld::GetSectorIndexY(pos.y); + for(ix = ixmin; ix < ixmax; ix++){ + + if(CRenderer::m_loadingPriority && ms_numModelsRequested > 5) + return; + + dx = ix - CWorld::GetSectorIndexX(pos.x); + d = dx*dx + dy*dy; + sect = CWorld::GetSector(ix, iy); + if(d <= 1){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + }else if(d <= 4*4){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], pos.x, pos.y, xmin, ymin, xmax, ymax); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax) +{ + CPtrNode *node; + CEntity *e; + float lodDistSq; + CVector2D pos; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + + if(e->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + + e->m_scanCode = CWorld::GetCurrentScanCode(); + if(!e->bStreamingDontDelete && !e->bIsSubway && + (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){ + CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()); + if(mi->m_type != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())){ + lodDistSq = sq(mi->GetLargestLodDistance()); + lodDistSq = min(lodDistSq, sq(STREAM_DIST)); + pos = CVector2D(e->GetPosition()); + if(xmin < pos.x && pos.x < xmax && + ymin < pos.y && pos.y < ymax && + (CVector2D(x, y) - pos).MagnitudeSqr() < lodDistSq) + if(CRenderer::IsEntityCullZoneVisible(e)) + RequestModel(e->GetModelIndex(), 0); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + + if(e->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + + e->m_scanCode = CWorld::GetCurrentScanCode(); + if(!e->bStreamingDontDelete && !e->bIsSubway && + (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){ + CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()); + if(mi->m_type != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())) + if(CRenderer::IsEntityCullZoneVisible(e)) + RequestModel(e->GetModelIndex(), 0); + } + } +} + +void +CStreaming::DeleteFarAwayRwObjects(const CVector &pos) +{ + int posx, posy; + int x, y; + int r, i; + CSector *sect; + + posx = CWorld::GetSectorIndexX(pos.x); + posy = CWorld::GetSectorIndexY(pos.y); + + // Move oldSectorX/Y to new sector and delete RW objects in its "wake" for every step: + // O is the old sector, <- is the direction in which we move it, + // X are the sectors we delete RW objects from (except we go up to 10) + // X + // X X + // X X X + // X X X + // <- O X X X + // X X X + // X X X + // X X + // X + + while(posx != ms_oldSectorX){ + if(posx < ms_oldSectorX){ + for(r = 2; r <= 10; r++){ + x = ms_oldSectorX + r; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + for(i = -r; i <= r; i++){ + y = ms_oldSectorY + i; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorX--; + }else{ + for(r = 2; r <= 10; r++){ + x = ms_oldSectorX - r; + if(x < 0) + break; + if(x >= NUMSECTORS_X) + continue; + + for(i = -r; i <= r; i++){ + y = ms_oldSectorY + i; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorX++; + } + } + + while(posy != ms_oldSectorY){ + if(posy < ms_oldSectorY){ + for(r = 2; r <= 10; r++){ + y = ms_oldSectorY + r; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + for(i = -r; i <= r; i++){ + x = ms_oldSectorX + i; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorY--; + }else{ + for(r = 2; r <= 10; r++){ + y = ms_oldSectorY - r; + if(y < 0) + break; + if(y >= NUMSECTORS_Y) + continue; + + for(i = -r; i <= r; i++){ + x = ms_oldSectorX + i; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorY++; + } + } +} + +void +CStreaming::DeleteAllRwObjects(void) +{ + int x, y; + CSector *sect; + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++){ + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + } +} + +void +CStreaming::DeleteRwObjectsAfterDeath(const CVector &pos) +{ + int ix, iy; + int x, y; + CSector *sect; + + ix = CWorld::GetSectorIndexX(pos.x); + iy = CWorld::GetSectorIndexX(pos.y); + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++) + if(fabs(ix - x) > 3.0f && + fabs(iy - y) > 3.0f){ + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + } +} + +void +CStreaming::DeleteRwObjectsBehindCamera(int32 mem) +{ + int ix, iy; + int x, y; + int xmin, xmax, ymin, ymax; + int inc; + CSector *sect; + + if(ms_memoryUsed < mem) + return; + + ix = CWorld::GetSectorIndexX(TheCamera.GetPosition().x); + iy = CWorld::GetSectorIndexX(TheCamera.GetPosition().y); + + if(fabs(TheCamera.GetForward().x) > fabs(TheCamera.GetForward().y)){ + // looking west/east + + ymin = max(iy - 10, 0); + ymax = min(iy + 10, NUMSECTORS_Y); + assert(ymin <= ymax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().x > 0){ + // looking east + xmax = max(ix - 2, 0); + xmin = max(ix - 10, 0); + inc = 1; + }else{ + // looking west + xmax = min(ix + 2, NUMSECTORS_X); + xmin = min(ix + 10, NUMSECTORS_X); + inc = -1; + } + for(y = ymin; y <= ymax; y++){ + for(x = xmin; x != xmax; x += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + // Now a block that intersects with the camera's frustum + if(TheCamera.GetForward().x > 0){ + // looking east + xmax = max(ix + 10, 0); + xmin = max(ix - 2, 0); + inc = 1; + }else{ + // looking west + xmax = min(ix - 10, NUMSECTORS_X); + xmin = min(ix + 2, NUMSECTORS_X); + inc = -1; + } + for(y = ymin; y <= ymax; y++){ + for(x = xmin; x != xmax; x += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + if(RemoveReferencedTxds(mem)) + return; + + // As last resort, delete objects from the last step more aggressively + for(y = ymin; y <= ymax; y++){ + for(x = xmax; x != xmin; x -= inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + }else{ + // looking north/south + + xmin = max(ix - 10, 0); + xmax = min(ix + 10, NUMSECTORS_X); + assert(xmin <= xmax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().y > 0){ + // looking north + ymax = max(iy - 2, 0); + ymin = max(iy - 10, 0); + inc = 1; + }else{ + // looking south + ymax = min(iy + 2, NUMSECTORS_Y); + ymin = min(iy + 10, NUMSECTORS_Y); + inc = -1; + } + for(x = xmin; x <= xmax; x++){ + for(y = ymin; y != ymax; y += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + // Now a block that intersects with the camera's frustum + if(TheCamera.GetForward().y > 0){ + // looking north + ymax = max(iy + 10, 0); + ymin = max(iy - 2, 0); + inc = 1; + }else{ + // looking south + ymax = min(iy - 10, NUMSECTORS_Y); + ymin = min(iy + 2, NUMSECTORS_Y); + inc = -1; + } + for(x = xmin; x <= xmax; x++){ + for(y = ymin; y != ymax; y += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + if(RemoveReferencedTxds(mem)) + return; + + // As last resort, delete objects from the last step more aggressively + for(x = xmin; x <= xmax; x++){ + for(y = ymax; y != ymin; y -= inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + } +} + +void +CStreaming::DeleteRwObjectsInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered) + e->DeleteRwObject(); + } +} + +void +CStreaming::DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(e->m_rwObject && !e->bStreamingDontDelete && !e->bImBeingRendered){ + // Now this is pretty weird... + if(fabs(CWorld::GetSectorIndexX(e->GetPosition().x) - x) >= 2.0f) +// { + e->DeleteRwObject(); +// return; // BUG? +// } + else // FIX? + if(fabs(CWorld::GetSectorIndexY(e->GetPosition().y) - y) >= 2.0f) + e->DeleteRwObject(); + } + } +} + +bool +CStreaming::DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, int32 mem) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered && + e->m_rwObject && ms_aInfoForModel[e->GetModelIndex()].m_next){ + e->DeleteRwObject(); + if(CModelInfo::GetModelInfo(e->GetModelIndex())->m_refCount == 0){ + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +bool +CStreaming::DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, int32 mem) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered && + e->m_rwObject && !e->IsVisible() && ms_aInfoForModel[e->GetModelIndex()].m_next){ + e->DeleteRwObject(); + if(CModelInfo::GetModelInfo(e->GetModelIndex())->m_refCount == 0){ + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +void +CStreaming::MakeSpaceFor(int32 size) +{ + // BUG: ms_memoryAvailable can be uninitialized + // the code still happens to work in that case because ms_memoryAvailable is unsigned + // but it's not nice.... + + while((uint32)ms_memoryUsed >= ms_memoryAvailable - size) + if(!RemoveLeastUsedModel()){ + DeleteRwObjectsBehindCamera(ms_memoryAvailable - size); + return; + } +} + +void +CStreaming::LoadScene(const CVector &pos) +{ + CStreamingInfo *si, *prev; + eLevelName level; + + level = CTheZones::GetLevelFromPosition(pos); + debug("Start load scene\n"); + for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){ + prev = si->m_prev; + if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0) + RemoveModel(si - ms_aInfoForModel); + } + CRenderer::m_loadingPriority = false; + CCullZones::ForceCullZoneCoors(pos); + DeleteAllRwObjects(); + AddModelsToRequestList(pos); + CRadar::StreamRadarSections(pos); + RemoveUnusedBigBuildings(level); + RequestBigBuildings(level); + LoadAllRequestedModels(false); + debug("End load scene\n"); +} + +void +CStreaming::MemoryCardSave(uint8 *buffer, uint32 *length) +{ + int i; + + *length = NUM_DEFAULT_MODELS; + for(i = 0; i < NUM_DEFAULT_MODELS; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + buffer[i] = ms_aInfoForModel[i].m_flags; + else + buffer[i] = 0xFF; +} + +void +CStreaming::MemoryCardLoad(uint8 *buffer, uint32 length) +{ + uint32 i; + + assert(length == NUM_DEFAULT_MODELS); + for(i = 0; i < length; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + if(buffer[i] != 0xFF) + ms_aInfoForModel[i].m_flags = buffer[i]; +} + +STARTPATCHES + InjectHook(0x406430, CStreaming::Init, PATCH_JUMP); + InjectHook(0x406C80, CStreaming::Shutdown, PATCH_JUMP); + InjectHook(0x4076C0, CStreaming::Update, PATCH_JUMP); + InjectHook(0x406CC0, (void (*)(void))CStreaming::LoadCdDirectory, PATCH_JUMP); + InjectHook(0x406DA0, (void (*)(const char*, int))CStreaming::LoadCdDirectory, PATCH_JUMP); + InjectHook(0x409740, CStreaming::ConvertBufferToObject, PATCH_JUMP); + InjectHook(0x409580, CStreaming::FinishLoadingLargeFile, PATCH_JUMP); + InjectHook(0x407EA0, CStreaming::RequestModel, PATCH_JUMP); + InjectHook(0x407FD0, CStreaming::RequestSubway, PATCH_JUMP); + InjectHook(0x408190, CStreaming::RequestBigBuildings, PATCH_JUMP); + InjectHook(0x408210, CStreaming::RequestIslands, PATCH_JUMP); + InjectHook(0x40A890, CStreaming::RequestSpecialModel, PATCH_JUMP); + InjectHook(0x40ADA0, CStreaming::RequestSpecialChar, PATCH_JUMP); + InjectHook(0x54A5F0, CStreaming::HasModelLoaded, PATCH_JUMP); + InjectHook(0x40ADC0, CStreaming::HasSpecialCharLoaded, PATCH_JUMP); + InjectHook(0x40ADE0, CStreaming::SetMissionDoesntRequireSpecialChar, PATCH_JUMP); + + InjectHook(0x408830, CStreaming::RemoveModel, PATCH_JUMP); + InjectHook(0x4083A0, CStreaming::RemoveUnusedBuildings, PATCH_JUMP); + InjectHook(0x4083D0, CStreaming::RemoveBuildings, PATCH_JUMP); + InjectHook(0x408640, CStreaming::RemoveUnusedBigBuildings, PATCH_JUMP); + InjectHook(0x408680, CStreaming::RemoveBigBuildings, PATCH_JUMP); + InjectHook(0x408780, CStreaming::RemoveIslandsNotUsed, PATCH_JUMP); + InjectHook(0x40B180, CStreaming::RemoveLoadedVehicle, PATCH_JUMP); + InjectHook(0x4089B0, CStreaming::RemoveLeastUsedModel, PATCH_JUMP); + InjectHook(0x408940, CStreaming::RemoveAllUnusedModels, PATCH_JUMP); + InjectHook(0x409450, CStreaming::RemoveReferencedTxds, PATCH_JUMP); + + InjectHook(0x40B160, CStreaming::GetAvailableVehicleSlot, PATCH_JUMP); + InjectHook(0x40B060, CStreaming::AddToLoadedVehiclesList, PATCH_JUMP); + InjectHook(0x4094C0, CStreaming::IsTxdUsedByRequestedModels, PATCH_JUMP); + InjectHook(0x407E70, CStreaming::IsObjectInCdImage, PATCH_JUMP); + InjectHook(0x408280, CStreaming::HaveAllBigBuildingsLoaded, PATCH_JUMP); + InjectHook(0x40A790, CStreaming::SetModelIsDeletable, PATCH_JUMP); + InjectHook(0x40A800, CStreaming::SetModelTxdIsDeletable, PATCH_JUMP); + InjectHook(0x40A820, CStreaming::SetMissionDoesntRequireModel, PATCH_JUMP); + + InjectHook(0x40AA00, CStreaming::LoadInitialPeds, PATCH_JUMP); + InjectHook(0x40ADF0, CStreaming::LoadInitialVehicles, PATCH_JUMP); + InjectHook(0x40AE60, CStreaming::StreamVehiclesAndPeds, PATCH_JUMP); + InjectHook(0x40AA30, CStreaming::StreamZoneModels, PATCH_JUMP); + InjectHook(0x40AD00, CStreaming::RemoveCurrentZonesModels, PATCH_JUMP); + + InjectHook(0x409BE0, CStreaming::ProcessLoadingChannel, PATCH_JUMP); + InjectHook(0x40A610, CStreaming::FlushChannels, PATCH_JUMP); + InjectHook(0x40A680, CStreaming::FlushRequestList, PATCH_JUMP); + InjectHook(0x409FF0, CStreaming::GetCdImageOffset, PATCH_JUMP); + InjectHook(0x409E50, CStreaming::GetNextFileOnCd, PATCH_JUMP); + InjectHook(0x40A060, CStreaming::RequestModelStream, PATCH_JUMP); + InjectHook(0x4077F0, CStreaming::RetryLoadFile, PATCH_JUMP); + InjectHook(0x40A390, CStreaming::LoadRequestedModels, PATCH_JUMP); + InjectHook(0x40A440, CStreaming::LoadAllRequestedModels, PATCH_JUMP); + + InjectHook(0x4078F0, CStreaming::AddModelsToRequestList, PATCH_JUMP); + InjectHook(0x407C50, (void (*)(CPtrList&,float,float,float,float,float,float))CStreaming::ProcessEntitiesInSectorList, PATCH_JUMP); + InjectHook(0x407DD0, (void (*)(CPtrList&))CStreaming::ProcessEntitiesInSectorList, PATCH_JUMP); + + InjectHook(0x407070, CStreaming::DeleteFarAwayRwObjects, PATCH_JUMP); + InjectHook(0x407390, CStreaming::DeleteAllRwObjects, PATCH_JUMP); + InjectHook(0x407400, CStreaming::DeleteRwObjectsAfterDeath, PATCH_JUMP); + InjectHook(0x408A60, CStreaming::DeleteRwObjectsBehindCamera, PATCH_JUMP); + InjectHook(0x407560, CStreaming::DeleteRwObjectsInSectorList, PATCH_JUMP); + InjectHook(0x4075A0, CStreaming::DeleteRwObjectsInOverlapSectorList, PATCH_JUMP); + InjectHook(0x409340, CStreaming::DeleteRwObjectsBehindCameraInSectorList, PATCH_JUMP); + InjectHook(0x4093C0, CStreaming::DeleteRwObjectsNotInFrustumInSectorList, PATCH_JUMP); + InjectHook(0x409B70, CStreaming::MakeSpaceFor, PATCH_JUMP); + InjectHook(0x40A6D0, CStreaming::LoadScene, PATCH_JUMP); + + InjectHook(0x40B210, CStreaming::MemoryCardSave, PATCH_JUMP); + InjectHook(0x40B250, CStreaming::MemoryCardLoad, PATCH_JUMP); + + InjectHook(0x4063E0, &CStreamingInfo::GetCdPosnAndSize, PATCH_JUMP); + InjectHook(0x406410, &CStreamingInfo::SetCdPosnAndSize, PATCH_JUMP); + InjectHook(0x4063D0, &CStreamingInfo::GetCdSize, PATCH_JUMP); + InjectHook(0x406380, &CStreamingInfo::AddToList, PATCH_JUMP); + InjectHook(0x4063A0, &CStreamingInfo::RemoveFromList, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Streaming.h b/src/core/Streaming.h new file mode 100644 index 00000000..212a6d71 --- /dev/null +++ b/src/core/Streaming.h @@ -0,0 +1,188 @@ +#pragma once + +#include "Game.h" + +enum { + STREAM_OFFSET_MODEL = 0, + STREAM_OFFSET_TXD = STREAM_OFFSET_MODEL+MODELINFOSIZE, + NUMSTREAMINFO = STREAM_OFFSET_TXD+TXDSTORESIZE +}; + +enum StreamFlags +{ + STREAMFLAGS_DONT_REMOVE = 0x01, + STREAMFLAGS_SCRIPTOWNED = 0x02, + STREAMFLAGS_DEPENDENCY = 0x04, // Is this right? + STREAMFLAGS_PRIORITY = 0x08, + STREAMFLAGS_NOFADE = 0x10, + + // TODO: this isn't named well, maybe CANT_REMOVE? + STREAMFLAGS_NOT_IN_LIST = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED, + STREAMFLAGS_KEEP_IN_MEMORY = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED|STREAMFLAGS_DEPENDENCY, +}; + +enum StreamLoadState +{ + STREAMSTATE_NOTLOADED = 0, + STREAMSTATE_LOADED = 1, + STREAMSTATE_INQUEUE = 2, + STREAMSTATE_READING = 3, // channel is reading + STREAMSTATE_STARTED = 4, // first part loaded +}; + +enum ChannelState +{ + CHANNELSTATE_IDLE = 0, + CHANNELSTATE_READING = 1, + CHANNELSTATE_STARTED = 2, + CHANNELSTATE_ERROR = 3, +}; + +class CStreamingInfo +{ +public: + CStreamingInfo *m_next; + CStreamingInfo *m_prev; + uint8 m_loadState; + uint8 m_flags; + + int16 m_nextID; + uint32 m_position; + uint32 m_size; + + bool GetCdPosnAndSize(uint32 &posn, uint32 &size); + void SetCdPosnAndSize(uint32 posn, uint32 size); + void AddToList(CStreamingInfo *link); + void RemoveFromList(void); + uint32 GetCdSize(void) { return m_size; } + bool IsPriority(void) { return !!(m_flags & STREAMFLAGS_PRIORITY); } +}; + +struct CStreamingChannel +{ + int32 streamIds[4]; + int32 offsets[4]; + int32 state; + int32 field24; + int32 position; + int32 size; + int32 numTries; + int32 status; // from CdStream +}; + +class CDirectory; +enum eLevelName; +class CPtrList; + +class CStreaming +{ +public: + static bool &ms_disableStreaming; + static bool &ms_bLoadingBigModel; + static int32 &ms_numModelsRequested; + static CStreamingInfo *ms_aInfoForModel; //[NUMSTREAMINFO] + static CStreamingInfo &ms_startLoadedList; + static CStreamingInfo &ms_endLoadedList; + static CStreamingInfo &ms_startRequestedList; + static CStreamingInfo &ms_endRequestedList; + static int32 &ms_oldSectorX; + static int32 &ms_oldSectorY; + static int32 &ms_streamingBufferSize; + static int8 **ms_pStreamingBuffer; //[2] + static int32 &ms_memoryUsed; + static CStreamingChannel *ms_channel; //[2] + static int32 &ms_channelError; + static int32 &ms_numVehiclesLoaded; + static int32 *ms_vehiclesLoaded; //[MAXVEHICLESLOADED] + static int32 &ms_lastVehicleDeleted; + static CDirectory *&ms_pExtraObjectsDir; + static int32 &ms_numPriorityRequests; + static bool &ms_hasLoadedLODs; + static int32 &ms_currentPedGrp; + static int32 ms_lastCullZone; + static uint16 &ms_loadedGangs; + static uint16 &ms_loadedGangCars; + static int32 ms_currentPedLoading; + static int32 *ms_imageOffsets; //[NUMCDIMAGES] + static int32 &ms_lastImageRead; + static int32 &ms_imageSize; + static uint32 &ms_memoryAvailable; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void LoadCdDirectory(void); + static void LoadCdDirectory(const char *dirname, int32 n); + static bool ConvertBufferToObject(int8 *buf, int32 streamId); + static bool FinishLoadingLargeFile(int8 *buf, int32 streamId); + static bool HasModelLoaded(int32 id) { return ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED; } + static void RequestModel(int32 model, int32 flags); + static void ReRequestModel(int32 model) { RequestModel(model, ms_aInfoForModel[model].m_flags); } + static void RequestTxd(int32 txd, int32 flags) { RequestModel(txd + STREAM_OFFSET_TXD, flags); } + static void ReRequestTxd(int32 txd) { ReRequestModel(txd + STREAM_OFFSET_TXD); } + static void RequestSubway(void); + static void RequestBigBuildings(eLevelName level); + static void RequestIslands(eLevelName level); + static void RequestSpecialModel(int32 modelId, const char *modelName, int32 flags); + static void RequestSpecialChar(int32 charId, const char *modelName, int32 flags); + static bool HasSpecialCharLoaded(int32 id); + static void SetMissionDoesntRequireSpecialChar(int32 id); + static void DecrementRef(int32 id); + static void RemoveModel(int32 id); + static void RemoveTxd(int32 id) { RemoveModel(id + STREAM_OFFSET_TXD); } + static void RemoveUnusedBuildings(eLevelName level); + static void RemoveBuildings(eLevelName level); + static void RemoveUnusedBigBuildings(eLevelName level); + static void RemoveIslandsNotUsed(eLevelName level); + static void RemoveBigBuildings(eLevelName level); + static bool RemoveLoadedVehicle(void); + static bool RemoveLeastUsedModel(void); + static void RemoveAllUnusedModels(void); + static void RemoveUnusedModelsInLoadedList(void); + static bool RemoveReferencedTxds(int32 mem); + static int32 GetAvailableVehicleSlot(void); + static bool IsTxdUsedByRequestedModels(int32 txdId); + static bool AddToLoadedVehiclesList(int32 modelId); + static bool IsObjectInCdImage(int32 id); + static void HaveAllBigBuildingsLoaded(eLevelName level); + static void SetModelIsDeletable(int32 id); + static void SetModelTxdIsDeletable(int32 id); + static void SetMissionDoesntRequireModel(int32 id); + static void LoadInitialPeds(void); + static void LoadInitialVehicles(void); + static void StreamVehiclesAndPeds(void); + static void StreamZoneModels(const CVector &pos); + static void RemoveCurrentZonesModels(void); + + static int32 GetCdImageOffset(int32 lastPosn); + static int32 GetNextFileOnCd(int32 position, bool priority); + static void RequestModelStream(int32 ch); + static bool ProcessLoadingChannel(int32 ch); + static void RetryLoadFile(int32 ch); + static void LoadRequestedModels(void); + static void LoadAllRequestedModels(bool priority); + static void FlushChannels(void); + static void FlushRequestList(void); + + static void MakeSpaceFor(int32 size); + static void ImGonnaUseStreamingMemory(void); + static void IHaveUsedStreamingMemory(void); + static void UpdateMemoryUsed(void); + + static void AddModelsToRequestList(const CVector &pos); + static void ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax); + static void ProcessEntitiesInSectorList(CPtrList &list); + static void DeleteFarAwayRwObjects(const CVector &pos); + static void DeleteAllRwObjects(void); + static void DeleteRwObjectsAfterDeath(const CVector &pos); + static void DeleteRwObjectsBehindCamera(int32 mem); + static void DeleteRwObjectsInSectorList(CPtrList &list); + static void DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y); + static bool DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, int32 mem); + static bool DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, int32 mem); + + static void LoadScene(const CVector &pos); + + static void MemoryCardSave(uint8 *buffer, uint32 *length); + static void MemoryCardLoad(uint8 *buffer, uint32 length); +}; diff --git a/src/core/SurfaceTable.cpp b/src/core/SurfaceTable.cpp new file mode 100644 index 00000000..2ba884b1 --- /dev/null +++ b/src/core/SurfaceTable.cpp @@ -0,0 +1,150 @@ +#include "common.h" +#include "patcher.h" +#include "main.h" +#include "FileMgr.h" +#include "Weather.h" +#include "Collision.h" +#include "SurfaceTable.h" + +float (*CSurfaceTable::ms_aAdhesiveLimitTable)[NUMADHESIVEGROUPS] = (float (*)[NUMADHESIVEGROUPS])0x8E29D4; + +void +CSurfaceTable::Initialise(char *filename) +{ + int lineno, fieldno; + char *line; + char surfname[256]; + float adhesiveLimit; + + CFileMgr::SetDir(""); + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + + line = (char*)work_buff; + for(lineno = 0; lineno < NUMADHESIVEGROUPS; lineno++){ + // skip white space and comments + while(*line == ' ' || *line == '\t' || *line == '\n' || *line == '\r' || *line == ';'){ + if(*line == ';'){ + while(*line != '\n' && *line != '\r') + line++; + }else + line++; + } + + sscanf(line, "%s", surfname); + // skip what we just read + while(!(*line == ' ' || *line == '\t' || *line == ',')) + line++; + + for(fieldno = 0; fieldno <= lineno; fieldno++){ + // skip white space + while(*line == ' ' || *line == '\t' || *line == ',') + line++; + adhesiveLimit = 0.0f; + if(*line != '-') + sscanf(line, "%f", &adhesiveLimit); + // skip what we just read + while(!(*line == ' ' || *line == '\t' || *line == ',' || *line == '\n')) + line++; + + ms_aAdhesiveLimitTable[lineno][fieldno] = adhesiveLimit; + ms_aAdhesiveLimitTable[fieldno][lineno] = adhesiveLimit; + } + } +} + +int +CSurfaceTable::GetAdhesionGroup(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_0: return ADHESIVE_ROAD; + case SURFACE_1: return ADHESIVE_ROAD; + case SURFACE_2: return ADHESIVE_LOOSE; + case SURFACE_3: return ADHESIVE_LOOSE; + case SURFACE_4: return ADHESIVE_HARD; + case SURFACE_5: return ADHESIVE_ROAD; + case SURFACE_6: return ADHESIVE_HARD; + case SURFACE_7: return ADHESIVE_HARD; + case SURFACE_8: return ADHESIVE_HARD; + case SURFACE_9: return ADHESIVE_HARD; + case SURFACE_10: return ADHESIVE_HARD; + case SURFACE_11: return ADHESIVE_HARD; + case SURFACE_12: return ADHESIVE_HARD; + case SURFACE_13: return ADHESIVE_HARD; + case SURFACE_14: return ADHESIVE_HARD; + case SURFACE_15: return ADHESIVE_HARD; + case SURFACE_16: return ADHESIVE_HARD; + case SURFACE_17: return ADHESIVE_RUBBER; + case SURFACE_18: return ADHESIVE_LOOSE; + case SURFACE_19: return ADHESIVE_WET; + case SURFACE_20: return ADHESIVE_ROAD; + case SURFACE_21: return ADHESIVE_ROAD; + case SURFACE_22: return ADHESIVE_ROAD; + case SURFACE_23: return ADHESIVE_RUBBER; + case SURFACE_24: return ADHESIVE_HARD; + case SURFACE_25: return ADHESIVE_LOOSE; + case SURFACE_26: return ADHESIVE_LOOSE; + case SURFACE_27: return ADHESIVE_HARD; + case SURFACE_28: return ADHESIVE_HARD; + case SURFACE_29: return ADHESIVE_RUBBER; + case SURFACE_30: return ADHESIVE_LOOSE; + case SURFACE_31: return ADHESIVE_HARD; + case SURFACE_32: return ADHESIVE_HARD; + default: return ADHESIVE_ROAD; + } +} + +float +CSurfaceTable::GetWetMultiplier(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_0: + case SURFACE_1: + case SURFACE_4: + case SURFACE_5: + case SURFACE_8: + case SURFACE_20: + case SURFACE_21: + case SURFACE_22: + case SURFACE_25: + case SURFACE_30: + case SURFACE_31: + return 1.0f - CWeather::WetRoads*0.25f; + + case SURFACE_2: + case SURFACE_6: + case SURFACE_7: + case SURFACE_9: + case SURFACE_10: + case SURFACE_11: + case SURFACE_12: + case SURFACE_13: + case SURFACE_14: + case SURFACE_15: + case SURFACE_16: + case SURFACE_17: + case SURFACE_23: + case SURFACE_24: + case SURFACE_26: + case SURFACE_27: + case SURFACE_28: + case SURFACE_29: + case SURFACE_32: + return 1.0f - CWeather::WetRoads*0.4f; + + default: + return 1.0f; + } +} + +float +CSurfaceTable::GetAdhesiveLimit(CColPoint &colpoint) +{ + return ms_aAdhesiveLimitTable[GetAdhesionGroup(colpoint.surfaceB)][GetAdhesionGroup(colpoint.surfaceA)]; +} + +STARTPATCHES + InjectHook(0x4AB8F0, CSurfaceTable::Initialise, PATCH_JUMP); + InjectHook(0x4ABA60, CSurfaceTable::GetAdhesionGroup, PATCH_JUMP); + InjectHook(0x4ABAA0, CSurfaceTable::GetWetMultiplier, PATCH_JUMP); + InjectHook(0x4ABA30, CSurfaceTable::GetAdhesiveLimit, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/SurfaceTable.h b/src/core/SurfaceTable.h new file mode 100644 index 00000000..e1882e69 --- /dev/null +++ b/src/core/SurfaceTable.h @@ -0,0 +1,106 @@ +#pragma once + + +enum +{ + SURFACE_0, + SURFACE_1, + SURFACE_2, + SURFACE_3, + SURFACE_4, + SURFACE_5, + SURFACE_6, + SURFACE_7, + SURFACE_8, + SURFACE_9, + SURFACE_10, + SURFACE_11, + SURFACE_12, + SURFACE_13, + SURFACE_14, + SURFACE_15, + SURFACE_16, + SURFACE_17, + SURFACE_18, + SURFACE_19, + SURFACE_20, + SURFACE_21, + SURFACE_22, + SURFACE_23, + SURFACE_24, + SURFACE_25, + SURFACE_26, + SURFACE_27, + SURFACE_28, + SURFACE_29, + SURFACE_30, + SURFACE_31, + SURFACE_32, + + NUMSURFACETYPES +}; + +// From nick +// TODO: check and use this +enum eSurfaceType +{ + SURFACE_DEFAULT, + SURFACE_TARMAC, + SURFACE_GRASS, + SURFACE_DIRT, + SURFACE_DIRTTRACK, + SURFACE_PAVEMENT, + SURFACE_METAL6, + SURFACE_GLASS, + SURFACE_SCAFFOLD, + SURFACE_METAL_DOOR, // garage door + SURFACE_BILLBOARD, + SURFACE_STEEL, //? + SURFACE_METAL_POLE, // ? + SURFACE_STREET_LIGHT, + SURFACE_METAL14, + SURFACE_METAL15, + SURFACE_METAL_FENCE, + SURFACE_FLESH, + SURFACE_SAND, + SURFACE_PUDDLE, + SURFACE_WOOD, + SURFACE_WOOD_BOX, + SURFACE_WOOD_PLANK, + SURFACE_TIRE, + SURFACE_HARD24, + SURFACE_HEDGE, + SURFACE_STONE, + SURFACE_METAL27, + SURFACE_METAL28, + SURFACE_RUBBER29, + SURFACE_LOOSE30, + SURFACE_BOLLARD, + SURFACE_GATE, + SURFACE_SAND33, + SURFACE_ROAD34, +}; + +enum +{ + ADHESIVE_RUBBER, + ADHESIVE_HARD, + ADHESIVE_ROAD, + ADHESIVE_LOOSE, + ADHESIVE_WET, + + NUMADHESIVEGROUPS +}; + +struct CColPoint; + +class CSurfaceTable +{ +// static float ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS]; + static float (*ms_aAdhesiveLimitTable)[NUMADHESIVEGROUPS]; +public: + static void Initialise(char *filename); + static int GetAdhesionGroup(uint8 surfaceType); + static float GetWetMultiplier(uint8 surfaceType); + static float GetAdhesiveLimit(CColPoint &colpoint); +}; diff --git a/src/core/TempColModels.cpp b/src/core/TempColModels.cpp new file mode 100644 index 00000000..a323d7c9 --- /dev/null +++ b/src/core/TempColModels.cpp @@ -0,0 +1,17 @@ +#include "common.h" +#include "patcher.h" +#include "TempColModels.h" + +CColModel &CTempColModels::ms_colModelPed1 = *(CColModel*)0x726CB0; +CColModel &CTempColModels::ms_colModelPed2 = *(CColModel*)0x726D08; +CColModel &CTempColModels::ms_colModelBBox = *(CColModel*)0x727FE0; +CColModel &CTempColModels::ms_colModelBumper1 = *(CColModel*)0x86BE88; +CColModel &CTempColModels::ms_colModelWheel1 = *(CColModel*)0x878C40; +CColModel &CTempColModels::ms_colModelPanel1 = *(CColModel*)0x87BDD8; +CColModel &CTempColModels::ms_colModelBodyPart2 = *(CColModel*)0x87BE30; +CColModel &CTempColModels::ms_colModelBodyPart1 = *(CColModel*)0x87BE88; +CColModel &CTempColModels::ms_colModelCutObj = *(CColModel*)0x87C960; +CColModel &CTempColModels::ms_colModelPedGroundHit = *(CColModel*)0x880480; +CColModel &CTempColModels::ms_colModelBoot1 = *(CColModel*)0x880670; +CColModel &CTempColModels::ms_colModelDoor1 = *(CColModel*)0x880850; +CColModel &CTempColModels::ms_colModelBonnet1 = *(CColModel*)0x8808A8; diff --git a/src/core/TempColModels.h b/src/core/TempColModels.h new file mode 100644 index 00000000..8ac74428 --- /dev/null +++ b/src/core/TempColModels.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Collision.h" + +class CTempColModels +{ +public: + static CColModel &ms_colModelPed1; + static CColModel &ms_colModelPed2; + static CColModel &ms_colModelBBox; + static CColModel &ms_colModelBumper1; + static CColModel &ms_colModelWheel1; + static CColModel &ms_colModelPanel1; + static CColModel &ms_colModelBodyPart2; + static CColModel &ms_colModelBodyPart1; + static CColModel &ms_colModelCutObj; + static CColModel &ms_colModelPedGroundHit; + static CColModel &ms_colModelBoot1; + static CColModel &ms_colModelDoor1; + static CColModel &ms_colModelBonnet1; +}; diff --git a/src/core/Text.cpp b/src/core/Text.cpp new file mode 100644 index 00000000..d7d63467 --- /dev/null +++ b/src/core/Text.cpp @@ -0,0 +1,232 @@ +#include "common.h" +#include "patcher.h" +#include "FileMgr.h" +#include "Frontend.h" +#include "Messages.h" +#include "Text.h" + +static wchar WideErrorString[25]; + +CText &TheText = *(CText*)0x941520; + +CText::CText(void) +{ + keyArray.entries = nil; + keyArray.numEntries = 0; + data.chars = nil; + data.numChars = 0; + encoding = 101; + memset(WideErrorString, 0, sizeof(WideErrorString)); +} + +CText::~CText(void) +{ + data.Unload(); + keyArray.Unload(); +} + +void +CText::Load(void) +{ + uint8 *filedata; + char filename[32], type[4]; + int length; + int offset, sectlen; + + Unload(); + filedata = new uint8[0x40000]; + + CFileMgr::SetDir("TEXT"); + switch(CMenuManager::m_PrefsLanguage){ + case LANGUAGE_AMERICAN: + sprintf(filename, "AMERICAN.GXT"); + break; + case LANGUAGE_FRENCH: + sprintf(filename, "FRENCH.GXT"); + break; + case LANGUAGE_GERMAN: + sprintf(filename, "GERMAN.GXT"); + break; + case LANGUAGE_ITALIAN: + sprintf(filename, "ITALIAN.GXT"); + break; + case LANGUAGE_SPANISH: + sprintf(filename, "SPANISH.GXT"); + break; + } + + length = CFileMgr::LoadFile(filename, filedata, 0x40000, "rb"); + CFileMgr::SetDir(""); + + offset = 0; + while(offset < length){ + type[0] = filedata[offset++]; + type[1] = filedata[offset++]; + type[2] = filedata[offset++]; + type[3] = filedata[offset++]; + sectlen = (int)filedata[offset+3]<<24 | (int)filedata[offset+2]<<16 | + (int)filedata[offset+1]<<8 | (int)filedata[offset+0]; + offset += 4; + if(sectlen != 0){ + if(strncmp(type, "TKEY", 4) == 0) + keyArray.Load(sectlen, filedata, &offset); + else if(strncmp(type, "TDAT", 4) == 0) + data.Load(sectlen, filedata, &offset); + else + offset += sectlen; + } + } + + keyArray.Update(data.chars); + + delete[] filedata; +} + +void +CText::Unload(void) +{ + CMessages::ClearAllMessagesDisplayedByGame(); + data.Unload(); + keyArray.Unload(); +} + +wchar* +CText::Get(const char *key) +{ + return keyArray.Search(key); +} + +wchar +CText::GetUpperCase(wchar c) +{ + // TODO: do this depending on encoding + if(islower(c)) + return toupper(c); + return c; +} + +void +CText::UpperCase(wchar *s) +{ + while(*s){ + *s = GetUpperCase(*s); + s++; + } +} + + +void +CKeyArray::Load(uint32 length, uint8 *data, int *offset) +{ + uint32 i; + uint8 *rawbytes; + + numEntries = length / sizeof(CKeyEntry); + entries = new CKeyEntry[numEntries]; + rawbytes = (uint8*)entries; + + for(i = 0; i < length; i++) + rawbytes[i] = data[(*offset)++]; +} + +void +CKeyArray::Unload(void) +{ + delete[] entries; + entries = nil; + numEntries = 0; +} + +void +CKeyArray::Update(wchar *chars) +{ + int i; + for(i = 0; i < numEntries; i++) + entries[i].value = (wchar*)((uint8*)chars + (uintptr)entries[i].value); +} + +CKeyEntry* +CKeyArray::BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high) +{ + int mid; + int diff; + + if(low > high) + return nil; + + mid = (low + high)/2; + diff = strcmp(key, entries[mid].key); + if(diff == 0) + return &entries[mid]; + if(diff < 0) + return BinarySearch(key, entries, low, mid-1); + if(diff > 0) + return BinarySearch(key, entries, mid+1, high); + return nil; +} + +wchar* +CKeyArray::Search(const char *key) +{ + CKeyEntry *found; + char errstr[25]; + int i; + + found = BinarySearch(key, entries, 0, numEntries-1); + if(found) + return found->value; + sprintf(errstr, "%s missing", key); + for(i = 0; i < 25; i++) + WideErrorString[i] = errstr[i]; + return WideErrorString; +} + + +void +CData::Load(uint32 length, uint8 *data, int *offset) +{ + uint32 i; + uint8 *rawbytes; + + numChars = length / sizeof(wchar); + chars = new wchar[numChars]; + rawbytes = (uint8*)chars; + + for(i = 0; i < length; i++) + rawbytes[i] = data[(*offset)++]; +} + +void +CData::Unload(void) +{ + delete[] chars; + chars = nil; + numChars = 0; +} + +void +AsciiToUnicode(const char *src, uint16 *dst) +{ + while((*dst++ = *src++) != '\0'); +} + +void +TextCopy(wchar *dst, const wchar *src) +{ + while((*dst++ = *src++) != '\0'); +} + +STARTPATCHES + InjectHook(0x52C3C0, &CText::Load, PATCH_JUMP); + InjectHook(0x52C580, &CText::Unload, PATCH_JUMP); + InjectHook(0x52C5A0, &CText::Get, PATCH_JUMP); + + InjectHook(0x52BE70, &CKeyArray::Load, PATCH_JUMP); + InjectHook(0x52BF60, &CKeyArray::Unload, PATCH_JUMP); + InjectHook(0x52BF80, &CKeyArray::Update, PATCH_JUMP); + InjectHook(0x52C060, &CKeyArray::BinarySearch, PATCH_JUMP); + InjectHook(0x52BFB0, &CKeyArray::Search, PATCH_JUMP); + + InjectHook(0x52C120, &CData::Load, PATCH_JUMP); + InjectHook(0x52C200, &CData::Unload, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Text.h b/src/core/Text.h new file mode 100644 index 00000000..2592e6b8 --- /dev/null +++ b/src/core/Text.h @@ -0,0 +1,52 @@ +#pragma once + +void AsciiToUnicode(const char *src, wchar *dst); +void TextCopy(wchar *dst, const wchar *src); + +struct CKeyEntry +{ + wchar *value; + char key[8]; +}; +// If this fails, CKeyArray::Load will have to be fixed +static_assert(sizeof(CKeyEntry) == 12, "CKeyEntry: error"); + +class CKeyArray +{ +public: + CKeyEntry *entries; + int numEntries; + + void Load(uint32 length, uint8 *data, int *offset); + void Unload(void); + void Update(wchar *chars); + CKeyEntry *BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high); + wchar *Search(const char *key); +}; + +class CData +{ +public: + wchar *chars; + int numChars; + + void Load(uint32 length, uint8 *data, int *offset); + void Unload(void); +}; + +class CText +{ + CKeyArray keyArray; + CData data; + int8 encoding; +public: + CText(void); + ~CText(void); + void Load(void); + void Unload(void); + wchar *Get(const char *key); + wchar GetUpperCase(wchar c); + void UpperCase(wchar *s); +}; + +extern CText &TheText; diff --git a/src/core/Timer.cpp b/src/core/Timer.cpp new file mode 100644 index 00000000..543f582b --- /dev/null +++ b/src/core/Timer.cpp @@ -0,0 +1,235 @@ +#include <windows.h> +#include "common.h" +#include "patcher.h" +#include "DMAudio.h" +#include "Record.h" +#include "Timer.h" + +uint32 &CTimer::m_snTimeInMilliseconds = *(uint32*)0x885B48; +uint32 &CTimer::m_snTimeInMillisecondsPauseMode = *(uint32*)0x5F7614; +uint32 &CTimer::m_snTimeInMillisecondsNonClipped = *(uint32*)0x9412E8; +uint32 &CTimer::m_snPreviousTimeInMilliseconds = *(uint32*)0x8F29E4; +uint32 &CTimer::m_FrameCounter = *(uint32*)0x9412EC; +float &CTimer::ms_fTimeScale = *(float*)0x8F2C20; +float &CTimer::ms_fTimeStep = *(float*)0x8E2CB4; +float &CTimer::ms_fTimeStepNonClipped = *(float*)0x8E2C4C; +bool &CTimer::m_UserPause = *(bool*)0x95CD7C; +bool &CTimer::m_CodePause = *(bool*)0x95CDB1; + +//UInt32 oldPcTimer; +uint32 &oldPcTimer = *(uint32*)0x9434F4; + +//UInt32 suspendPcTimer; +uint32 &suspendPcTimer = *(uint32*)0x62A308; + +//UInt32 _nCyclesPerMS = 1; +uint32 &_nCyclesPerMS = *(uint32*)0x5F7610; + +//LARGE_INTEGER _oldPerfCounter; +LARGE_INTEGER &_oldPerfCounter = *(LARGE_INTEGER*)0x62A310; + +//LARGE_INTEGER perfSuspendCounter; +LARGE_INTEGER &perfSuspendCounter = *(LARGE_INTEGER*)0x62A318; + +//UInt32 suspendDepth; +uint32 &suspendDepth = *(uint32*)0x62A320; + +void CTimer::Initialise(void) +{ + debug("Initialising CTimer...\n"); + + ms_fTimeScale = 1.0f; + ms_fTimeStep = 1.0f; + suspendDepth = 0; + m_UserPause = false; + m_CodePause = false; + m_snTimeInMillisecondsNonClipped = 0; + m_snPreviousTimeInMilliseconds = 0; + m_snTimeInMilliseconds = 1; + + LARGE_INTEGER perfFreq; + if ( QueryPerformanceFrequency(&perfFreq) ) + { + OutputDebugString("Performance counter available\n"); + _nCyclesPerMS = uint32(perfFreq.QuadPart / 1000); + QueryPerformanceCounter(&_oldPerfCounter); + } + else + { + OutputDebugString("Performance counter not available, using millesecond timer\n"); + _nCyclesPerMS = 0; + oldPcTimer = RsTimer(); + } + + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds; + + m_FrameCounter = 0; + + DMAudio.ResetTimers(m_snPreviousTimeInMilliseconds); + + debug("CTimer ready\n"); +} + +void CTimer::Shutdown(void) +{ + ; +} + +#if 1 +WRAPPER void CTimer::Update(void) { EAXJMP(0x4ACF70); } +#else +void CTimer::Update(void) +{ + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart) & 0x7FFFFFFF; + + _oldPerfCounter = pc; + + double updInCyclesScaled = (double)updInCycles * ms_fTimeScale; + + double upd = updInCyclesScaled / (double)_nCyclesPerMS; + + m_snTimeInMillisecondsPauseMode = (Int64)(m_snTimeInMillisecondsPauseMode + upd); + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = (Int64)(m_snTimeInMilliseconds + upd); + m_snTimeInMillisecondsNonClipped = (Int64)(m_snTimeInMillisecondsNonClipped + upd); + ms_fTimeStep = updInCyclesScaled / (double)_nCyclesPerMS / 20.0; + } + } + else + { + uint32 timer = RsTimer(); + + uint32 updInMs = timer - oldPcTimer; + + double upd = (double)updInMs * ms_fTimeScale; + + oldPcTimer = timer; + + m_snTimeInMillisecondsPauseMode = (Int64)(m_snTimeInMillisecondsPauseMode + upd); + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = (Int64)(m_snTimeInMilliseconds + upd); + m_snTimeInMillisecondsNonClipped = (Int64)(m_snTimeInMillisecondsNonClipped + upd); + ms_fTimeStep = upd / 1000.0f * 50.0f; + } + } + + if ( ms_fTimeStep < 0.01f && !GetIsPaused() ) + ms_fTimeStep = 0.01f; + + ms_fTimeStepNonClipped = ms_fTimeStep; + + if ( CRecordDataForGame::RecordingState != RECORDSTATE_2 ) + { + ms_fTimeStep = min(3.0f, ms_fTimeStep); + + if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 ) + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60; + } + + if ( CRecordDataForChase::Status == RECORDSTATE_1 ) + { + ms_fTimeStep = 1.0f; + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16; + } + + m_FrameCounter++; +} +#endif + +void CTimer::Suspend(void) +{ + if ( ++suspendDepth > 1 ) + return; + + if ( (double)_nCyclesPerMS != 0.0 ) + QueryPerformanceCounter(&perfSuspendCounter); + else + suspendPcTimer = RsTimer(); +} + +void CTimer::Resume(void) +{ + if ( --suspendDepth != 0 ) + return; + + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + _oldPerfCounter.LowPart += pc.LowPart - perfSuspendCounter.LowPart; + } + else + oldPcTimer += RsTimer() - suspendPcTimer; +} + +uint32 CTimer::GetCyclesPerMillisecond(void) +{ + if (_nCyclesPerMS != 0) + return _nCyclesPerMS; + else + return 1; +} + +uint32 CTimer::GetCurrentTimeInCycles(void) +{ + if ( _nCyclesPerMS != 0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + return (pc.LowPart - _oldPerfCounter.LowPart) & 0x7FFFFFFF; + } + else + return RsTimer() - oldPcTimer; +} + +bool CTimer::GetIsSlowMotionActive(void) +{ + return ms_fTimeScale < 1.0f; +} + +void CTimer::Stop(void) +{ + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; +} + +void CTimer::StartUserPause(void) +{ + m_UserPause = true; +} + +void CTimer::EndUserPause(void) +{ + m_UserPause = false; +} + +#if 0 +STARTPATCHES + InjectHook(0x4ACE60, CTimer::Initialise, PATCH_JUMP); + InjectHook(0x4ACF60, CTimer::Shutdown, PATCH_JUMP); + InjectHook(0x4ACF70, CTimer::Update, PATCH_JUMP); + InjectHook(0x4AD310, CTimer::Suspend, PATCH_JUMP); + InjectHook(0x4AD370, CTimer::Resume, PATCH_JUMP); + InjectHook(0x4AD3F0, CTimer::GetCyclesPerMillisecond, PATCH_JUMP); + InjectHook(0x4AD410, CTimer::GetCurrentTimeInCycles, PATCH_JUMP); + InjectHook(0x4AD450, CTimer::GetIsSlowMotionActive, PATCH_JUMP); + InjectHook(0x4AD480, CTimer::Stop, PATCH_JUMP); + InjectHook(0x4AD490, CTimer::StartUserPause, PATCH_JUMP); + InjectHook(0x4AD4A0, CTimer::EndUserPause, PATCH_JUMP); +ENDPATCHES +#endif diff --git a/src/core/Timer.h b/src/core/Timer.h new file mode 100644 index 00000000..9e6d447e --- /dev/null +++ b/src/core/Timer.h @@ -0,0 +1,51 @@ +#pragma once + +class CTimer +{ + static uint32 &m_snTimeInMilliseconds; + static uint32 &m_snTimeInMillisecondsPauseMode; + static uint32 &m_snTimeInMillisecondsNonClipped; + static uint32 &m_snPreviousTimeInMilliseconds; + static uint32 &m_FrameCounter; + static float &ms_fTimeScale; + static float &ms_fTimeStep; + static float &ms_fTimeStepNonClipped; + static bool &m_UserPause; + static bool &m_CodePause; +public: + static float GetTimeStep(void) { return ms_fTimeStep; } + static void SetTimeStep(float ts) { ms_fTimeStep = ts; } + static float GetTimeStepInSeconds() { return ms_fTimeStep / 50.0f; } + static float GetTimeStepInMilliseconds() { return ms_fTimeStep / 50.0f * 1000.0f; } + static float GetTimeStepNonClipped(void) { return ms_fTimeStepNonClipped; } + static float GetTimeStepNonClippedInSeconds(void) { return ms_fTimeStepNonClipped / 50.0f; } + static void SetTimeStepNonClipped(float ts) { ms_fTimeStepNonClipped = ts; } + static uint32 GetFrameCounter(void) { return m_FrameCounter; } + static void SetFrameCounter(uint32 fc) { m_FrameCounter = fc; } + static uint32 GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; } + static void SetTimeInMilliseconds(uint32 t) { m_snTimeInMilliseconds = t; } + static uint32 GetTimeInMillisecondsNonClipped(void) { return m_snTimeInMillisecondsNonClipped; } + static void SetTimeInMillisecondsNonClipped(uint32 t) { m_snTimeInMillisecondsNonClipped = t; } + static uint32 GetTimeInMillisecondsPauseMode(void) { return m_snTimeInMillisecondsPauseMode; } + static void SetTimeInMillisecondsPauseMode(uint32 t) { m_snTimeInMillisecondsPauseMode = t; } + static uint32 GetPreviousTimeInMilliseconds(void) { return m_snPreviousTimeInMilliseconds; } + static void SetPreviousTimeInMilliseconds(uint32 t) { m_snPreviousTimeInMilliseconds = t; } + static float GetTimeScale(void) { return ms_fTimeScale; } + static void SetTimeScale(float ts) { ms_fTimeScale = ts; } + + static bool GetIsPaused() { return m_UserPause || m_CodePause; } + static bool GetIsUserPaused() { return m_UserPause; } + static void SetCodePause(bool pause) { m_CodePause = pause; } + + static void Initialise(void); + static void Shutdown(void); + static void Update(void); + static void Suspend(void); + static void Resume(void); + static uint32 GetCyclesPerMillisecond(void); + static uint32 GetCurrentTimeInCycles(void); + static bool GetIsSlowMotionActive(void); + static void Stop(void); + static void StartUserPause(void); + static void EndUserPause(void); +}; diff --git a/src/core/TxdStore.cpp b/src/core/TxdStore.cpp new file mode 100644 index 00000000..5085c7e4 --- /dev/null +++ b/src/core/TxdStore.cpp @@ -0,0 +1,208 @@ +#include "common.h" +#include "patcher.h" +#include "templates.h" +#include "Streaming.h" +#include "RwHelper.h" +#include "TxdStore.h" + +CPool<TxdDef,TxdDef> *&CTxdStore::ms_pTxdPool = *(CPool<TxdDef,TxdDef>**)0x8F5FB8; +RwTexDictionary *&CTxdStore::ms_pStoredTxd = *(RwTexDictionary**)0x9405BC; + +void +CTxdStore::Initialize(void) +{ + if(ms_pTxdPool == nil) + ms_pTxdPool = new CPool<TxdDef,TxdDef>(TXDSTORESIZE); +} + +void +CTxdStore::Shutdown(void) +{ + if(ms_pTxdPool) + delete ms_pTxdPool; +} + +void +CTxdStore::GameShutdown(void) +{ + int i; + + for(i = 0; i < TXDSTORESIZE; i++){ + TxdDef *def = GetSlot(i); + if(def && GetNumRefs(i) == 0) + RemoveTxdSlot(i); + } +} + +int +CTxdStore::AddTxdSlot(const char *name) +{ + TxdDef *def = ms_pTxdPool->New(); + assert(def); + def->texDict = nil; + def->refCount = 0; + strcpy(def->name, name); + return ms_pTxdPool->GetJustIndex(def); +} + +void +CTxdStore::RemoveTxdSlot(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def->texDict) + RwTexDictionaryDestroy(def->texDict); + ms_pTxdPool->Delete(def); +} + +int +CTxdStore::FindTxdSlot(const char *name) +{ + char *defname; + int size = ms_pTxdPool->GetSize(); + for(int i = 0; i < size; i++){ + defname = GetTxdName(i); + if(defname && _strcmpi(defname, name) == 0) + return i; + } + return -1; +} + +char* +CTxdStore::GetTxdName(int slot) +{ + TxdDef *def = GetSlot(slot); + return def ? def->name : nil; +} + +void +CTxdStore::PushCurrentTxd(void) +{ + ms_pStoredTxd = RwTexDictionaryGetCurrent(); +} + +void +CTxdStore::PopCurrentTxd(void) +{ + RwTexDictionarySetCurrent(ms_pStoredTxd); + ms_pStoredTxd = nil; +} + +void +CTxdStore::SetCurrentTxd(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def) + RwTexDictionarySetCurrent(def->texDict); +} + +void +CTxdStore::Create(int slot) +{ + GetSlot(slot)->texDict = RwTexDictionaryCreate(); +} + +int +CTxdStore::GetNumRefs(int slot) +{ + return GetSlot(slot)->refCount; +} + +void +CTxdStore::AddRef(int slot) +{ + GetSlot(slot)->refCount++; +} + +void +CTxdStore::RemoveRef(int slot) +{ + if(--GetSlot(slot)->refCount <= 0) + CStreaming::RemoveModel(slot + STREAM_OFFSET_TXD); +} + +void +CTxdStore::RemoveRefWithoutDelete(int slot) +{ + GetSlot(slot)->refCount--; +} + +bool +CTxdStore::LoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){ + def->texDict = RwTexDictionaryGtaStreamRead(stream); + return def->texDict != nil; + } + printf("Failed to load TXD\n"); + return false; +} + +bool +CTxdStore::LoadTxd(int slot, const char *filename) +{ + RwStream *stream; + bool ret; + + ret = false; + _rwD3D8TexDictionaryEnableRasterFormatConversion(true); + do + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + while(stream == nil); + ret = LoadTxd(slot, stream); + RwStreamClose(stream, nil); + return ret; +} + +bool +CTxdStore::StartLoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){ + def->texDict = RwTexDictionaryGtaStreamRead1(stream); + return def->texDict != nil; + }else{ + printf("Failed to load TXD\n"); + return false; + } +} + +bool +CTxdStore::FinishLoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + def->texDict = RwTexDictionaryGtaStreamRead2(stream, def->texDict); + return def->texDict != nil; +} + +void +CTxdStore::RemoveTxd(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def->texDict) + RwTexDictionaryDestroy(def->texDict); + def->texDict = nil; +} + +STARTPATCHES + InjectHook(0x527440, CTxdStore::Initialize, PATCH_JUMP); + InjectHook(0x527470, CTxdStore::Shutdown, PATCH_JUMP); + InjectHook(0x527490, CTxdStore::GameShutdown, PATCH_JUMP); + InjectHook(0x5274E0, CTxdStore::AddTxdSlot, PATCH_JUMP); + InjectHook(0x5275D0, CTxdStore::FindTxdSlot, PATCH_JUMP); + InjectHook(0x527590, CTxdStore::GetTxdName, PATCH_JUMP); + InjectHook(0x527900, CTxdStore::PushCurrentTxd, PATCH_JUMP); + InjectHook(0x527910, CTxdStore::PopCurrentTxd, PATCH_JUMP); + InjectHook(0x5278C0, CTxdStore::SetCurrentTxd, PATCH_JUMP); + InjectHook(0x527830, CTxdStore::Create, PATCH_JUMP); + InjectHook(0x527A00, CTxdStore::GetNumRefs, PATCH_JUMP); + InjectHook(0x527930, CTxdStore::AddRef, PATCH_JUMP); + InjectHook(0x527970, CTxdStore::RemoveRef, PATCH_JUMP); + InjectHook(0x5279C0, CTxdStore::RemoveRefWithoutDelete, PATCH_JUMP); + InjectHook(0x527700, (bool (*)(int, RwStream*))CTxdStore::LoadTxd, PATCH_JUMP); + InjectHook(0x5276B0, (bool (*)(int, const char*))CTxdStore::LoadTxd, PATCH_JUMP); + InjectHook(0x527770, CTxdStore::StartLoadTxd, PATCH_JUMP); + InjectHook(0x5277E0, CTxdStore::FinishLoadTxd, PATCH_JUMP); + InjectHook(0x527870, CTxdStore::RemoveTxd, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/TxdStore.h b/src/core/TxdStore.h new file mode 100644 index 00000000..a9e57d31 --- /dev/null +++ b/src/core/TxdStore.h @@ -0,0 +1,44 @@ +#pragma once + +#include "templates.h" + +struct TxdDef { + RwTexDictionary *texDict; + int refCount; + char name[20]; +}; + +class CTxdStore +{ + static CPool<TxdDef,TxdDef> *&ms_pTxdPool; + static RwTexDictionary *&ms_pStoredTxd; +public: + static void Initialize(void); + static void Shutdown(void); + static void GameShutdown(void); + static int AddTxdSlot(const char *name); + static void RemoveTxdSlot(int slot); + static int FindTxdSlot(const char *name); + static char *GetTxdName(int slot); + static void PushCurrentTxd(void); + static void PopCurrentTxd(void); + static void SetCurrentTxd(int slot); + static void Create(int slot); + static int GetNumRefs(int slot); + static void AddRef(int slot); + static void RemoveRef(int slot); + static void RemoveRefWithoutDelete(int slot); + static bool LoadTxd(int slot, RwStream *stream); + static bool LoadTxd(int slot, const char *filename); + static bool StartLoadTxd(int slot, RwStream *stream); + static bool FinishLoadTxd(int slot, RwStream *stream); + static void RemoveTxd(int slot); + + static TxdDef *GetSlot(int slot) { + assert(slot >= 0); + assert(ms_pTxdPool); + assert(slot < ms_pTxdPool->GetSize()); + return ms_pTxdPool->GetSlot(slot); + } + static bool isTxdLoaded(int slot); +}; diff --git a/src/core/User.cpp b/src/core/User.cpp new file mode 100644 index 00000000..c9cb97cc --- /dev/null +++ b/src/core/User.cpp @@ -0,0 +1,176 @@ +#include "common.h" +#include "patcher.h" + +#include "DMAudio.h" +#include "Hud.h" +#include "Replay.h" +#include "Timer.h" +#include "Script.h" +#include "User.h" + +CPlaceName& CUserDisplay::PlaceName = *(CPlaceName*)0x8F29BC; +COnscreenTimer& CUserDisplay::OnscnTimer = *(COnscreenTimer*)0x862238; +CPager& CUserDisplay::Pager = *(CPager*)0x8F2744; +CCurrentVehicle& CUserDisplay::CurrentVehicle = *(CCurrentVehicle*)0x8F5FE8; + +void COnscreenTimer::Init() { + m_bDisabled = false; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + m_sEntries[i].m_nTimerOffset = 0; + m_sEntries[i].m_nCounterOffset = 0; + + for(uint32 j = 0; j < 10; j++) { + m_sEntries[i].m_aTimerText[j] = 0; + m_sEntries[i].m_aCounterText[j] = 0; + } + + m_sEntries[i].m_nType = 0; + m_sEntries[i].m_bTimerProcessed = 0; + m_sEntries[i].m_bCounterProcessed = 0; + } +} + +void COnscreenTimer::Process() { + if(!CReplay::IsPlayingBack() && !m_bDisabled) { + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + m_sEntries[i].Process(); + } + } +} + +void COnscreenTimer::ProcessForDisplay() { + if(CHud::m_Wants_To_Draw_Hud) { + m_bProcessed = false; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(m_sEntries[i].ProcessForDisplay()) { + m_bProcessed = true; + } + } + } +} + +void COnscreenTimer::ClearCounter(uint32 offset) { + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(offset == m_sEntries[i].m_nCounterOffset) { + m_sEntries[i].m_nCounterOffset = 0; + m_sEntries[i].m_aCounterText[0] = 0; + m_sEntries[i].m_nType = 0; + m_sEntries[i].m_bCounterProcessed = 0; + } + } +} + +void COnscreenTimer::ClearClock(uint32 offset) { + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(offset == m_sEntries[i].m_nTimerOffset) { + m_sEntries[i].m_nTimerOffset = 0; + m_sEntries[i].m_aTimerText[0] = 0; + m_sEntries[i].m_bTimerProcessed = 0; + } + } +} + +void COnscreenTimer::AddCounter(uint32 offset, uint16 type, char* text) { + uint32 i = 0; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(m_sEntries[i].m_nCounterOffset == 0) { + break; + } + return; + } + + m_sEntries[i].m_nCounterOffset = offset; + if(text) { + strncpy(m_sEntries[i].m_aCounterText, text, 10); + } else { + m_sEntries[i].m_aCounterText[0] = 0; + } + + m_sEntries[i].m_nType = type; +} + +void COnscreenTimer::AddClock(uint32 offset, char* text) { + uint32 i = 0; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(m_sEntries[i].m_nTimerOffset == 0) { + break; + } + return; + } + + m_sEntries[i].m_nTimerOffset = offset; + if(text) { + strncpy(m_sEntries[i].m_aTimerText, text, 10); + } else { + m_sEntries[i].m_aTimerText[0] = 0; + } +} + +void COnscreenTimerEntry::Process() { + if(m_nTimerOffset == 0) { + return; + } + + uint32* timerPtr = (uint32*)&CTheScripts::ScriptSpace[m_nTimerOffset]; + uint32 oldTime = *timerPtr; + int32 newTime = int32(oldTime - uint32(20.0f * CTimer::GetTimeStep())); + if(newTime < 0) { + *timerPtr = 0; + m_bTimerProcessed = 0; + m_nTimerOffset = 0; + m_aTimerText[0] = 0; + } else { + *timerPtr = (uint32)newTime; + uint32 oldTimeSeconds = oldTime / 1000; + if(oldTimeSeconds <= 11 && newTime / 1000 != oldTimeSeconds) { + // TODO: use an enum here + DMAudio.PlayFrontEndSound(0x93, newTime / 1000); + } + } +} + +bool COnscreenTimerEntry::ProcessForDisplay() { + m_bTimerProcessed = false; + m_bCounterProcessed = false; + + if(m_nTimerOffset == 0 && m_nCounterOffset == 0) { + return false; + } + + if(m_nTimerOffset != 0) { + m_bTimerProcessed = true; + ProcessForDisplayTimer(); + } + + if(m_nCounterOffset != 0) { + m_bCounterProcessed = true; + ProcessForDisplayCounter(); + } + return true; +} + +int COnscreenTimerEntry::ProcessForDisplayTimer() { + uint32 time = *(uint32*)&CTheScripts::ScriptSpace[m_nTimerOffset]; + return sprintf(m_bTimerBuffer, "%02d:%02d", time / 1000 / 60, + time / 1000 % 60); +} + +int COnscreenTimerEntry::ProcessForDisplayCounter() { + uint32 counter = *(uint32*)&CTheScripts::ScriptSpace[m_nCounterOffset]; + return sprintf(m_bCounterBuffer, "%d", counter); +} + +STARTPATCHES + InjectHook(0x429160, &COnscreenTimerEntry::Process, PATCH_JUMP); + InjectHook(0x429110, &COnscreenTimerEntry::ProcessForDisplay, PATCH_JUMP); + InjectHook(0x429080, &COnscreenTimerEntry::ProcessForDisplayTimer, PATCH_JUMP); + InjectHook(0x4290F0, &COnscreenTimerEntry::ProcessForDisplayCounter, PATCH_JUMP); + + InjectHook(0x429220, &COnscreenTimer::Init, PATCH_JUMP); + InjectHook(0x429320, &COnscreenTimer::Process, PATCH_JUMP); + InjectHook(0x4292E0, &COnscreenTimer::ProcessForDisplay, PATCH_JUMP); + InjectHook(0x429450, &COnscreenTimer::ClearCounter, PATCH_JUMP); + InjectHook(0x429410, &COnscreenTimer::ClearClock, PATCH_JUMP); + InjectHook(0x4293B0, &COnscreenTimer::AddCounter, PATCH_JUMP); + InjectHook(0x429350, &COnscreenTimer::AddClock, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/User.h b/src/core/User.h new file mode 100644 index 00000000..8b744c7e --- /dev/null +++ b/src/core/User.h @@ -0,0 +1,64 @@ +#pragma once + +class COnscreenTimerEntry +{ +public: + uint32 m_nTimerOffset; + uint32 m_nCounterOffset; + char m_aTimerText[10]; + char m_aCounterText[10]; + uint16 m_nType; + char m_bCounterBuffer[42]; + char m_bTimerBuffer[42]; + bool m_bTimerProcessed; + bool m_bCounterProcessed; + + void Process(); + bool ProcessForDisplay(); + + int ProcessForDisplayTimer(); + int ProcessForDisplayCounter(); +}; + +static_assert(sizeof(COnscreenTimerEntry) == 0x74, "COnscreenTimerEntry: error"); + +class COnscreenTimer +{ +public: + COnscreenTimerEntry m_sEntries[NUMONSCREENTIMERENTRIES]; + bool m_bProcessed; + bool m_bDisabled; + + void Init(); + void Process(); + void ProcessForDisplay(); + + void ClearCounter(uint32 offset); + void ClearClock(uint32 offset); + + void AddCounter(uint32 offset, uint16 type, char* text); + void AddClock(uint32 offset, char* text); +}; + +static_assert(sizeof(COnscreenTimer) == 0x78, "COnscreenTimer: error"); + +class CPlaceName +{ +}; + +class CCurrentVehicle +{ +}; + +class CPager +{ +}; + +class CUserDisplay +{ +public: + static CPlaceName &PlaceName; + static COnscreenTimer &OnscnTimer; + static CPager &Pager; + static CCurrentVehicle &CurrentVehicle; +}; diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp new file mode 100644 index 00000000..21853308 --- /dev/null +++ b/src/core/Wanted.cpp @@ -0,0 +1,129 @@ +#include "common.h" +#include "patcher.h" +#include "Wanted.h" + +int32 &CWanted::MaximumWantedLevel = *(int32*)0x5F7714; + +bool CWanted::AreSwatRequired() +{ + return m_nWantedLevel >= 4; +} + +bool CWanted::AreFbiRequired() +{ + return m_nWantedLevel >= 5; +} + +bool CWanted::AreArmyRequired() +{ + return m_nWantedLevel >= 6; +} + +int CWanted::NumOfHelisRequired() +{ + if (m_IsIgnoredByCops) + return 0; + + // Return value is number of helicopters, no need to name them. + switch (m_nWantedLevel) { + case WANTEDLEVEL_3: + case WANTEDLEVEL_4: + return 1; + case WANTEDLEVEL_5: + case WANTEDLEVEL_6: + return 2; + default: + return 0; + }; +} + +void CWanted::SetWantedLevel(int32 level) +{ + ClearQdCrimes(); + switch (level) { + case NOTWANTED: + m_nChaos = 0; + break; + case WANTEDLEVEL_1: + m_nChaos = 60; + break; + case WANTEDLEVEL_2: + m_nChaos = 220; + break; + case WANTEDLEVEL_3: + m_nChaos = 420; + break; + case WANTEDLEVEL_4: + m_nChaos = 820; + break; + case WANTEDLEVEL_5: + m_nChaos = 1620; + break; + case WANTEDLEVEL_6: + m_nChaos = 3220; + break; + default: + if (level > MaximumWantedLevel) + m_nChaos = MaximumWantedLevel; + break; + } + UpdateWantedLevel(); +} + +void CWanted::ClearQdCrimes() +{ + for (int i = 0; i < 16; i++) { + m_sCrimes[i].m_eCrimeType = CRIME_NONE; + }; +} + +void CWanted::UpdateWantedLevel() +{ + int32 CurrWantedLevel = m_nWantedLevel; + + if (m_nChaos >= 0 && m_nChaos < 40) { + m_nWantedLevel = NOTWANTED; + m_MaximumLawEnforcerVehicles = 0; + m_MaxCops = 0; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 40 && m_nChaos < 200) { + m_nWantedLevel = WANTEDLEVEL_1; + m_MaximumLawEnforcerVehicles = 1; + m_MaxCops = 1; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 200 && m_nChaos < 400) { + m_nWantedLevel = WANTEDLEVEL_2; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 3; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 400 && m_nChaos < 800) { + m_nWantedLevel = WANTEDLEVEL_3; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 4; + m_RoadblockDensity = 4; + } + else if (m_nChaos >= 800 && m_nChaos < 1600) { + m_nWantedLevel = WANTEDLEVEL_4; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 6; + m_RoadblockDensity = 8; + } + else if (m_nChaos >= 1600 && m_nChaos < 3200) { + m_nWantedLevel = WANTEDLEVEL_5; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 8; + m_RoadblockDensity = 10; + } + else if (m_nChaos >= 3200) { + m_nWantedLevel = WANTEDLEVEL_6; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 10; + m_RoadblockDensity = 12; + } + + if (CurrWantedLevel != m_nWantedLevel) + m_nLastWantedLevelChange = CTimer::GetTimeInMilliseconds(); +}
\ No newline at end of file diff --git a/src/core/Wanted.h b/src/core/Wanted.h new file mode 100644 index 00000000..d14bb905 --- /dev/null +++ b/src/core/Wanted.h @@ -0,0 +1,49 @@ +#pragma once +#include "Entity.h" +#include "math/Vector.h" +#include "CopPed.h" + +enum eWantedLevel { + NOTWANTED, + WANTEDLEVEL_1, + WANTEDLEVEL_2, + WANTEDLEVEL_3, + WANTEDLEVEL_4, + WANTEDLEVEL_5, + WANTEDLEVEL_6, +}; + +class CWanted +{ +public: + int32 m_nChaos; + int32 m_nLastUpdateTime; + int32 m_nLastWantedLevelChange; + float m_fCrimeSensitivity; + uint8 m_CurrentCops; + uint8 m_MaxCops; + uint8 m_MaximumLawEnforcerVehicles; + int8 field_19; + int16 m_RoadblockDensity; + uint8 m_IsIgnoredByCops : 1; + uint8 m_IsIgnoredByEveryOne : 1; + uint8 m_IsSwatRequired : 1; + uint8 m_IsFbiRequired : 1; + uint8 m_IdArmyRequired : 1; + int8 field_23; + int32 m_nWantedLevel; + CCrime m_sCrimes[16]; + CCopPed *m_pCops[10]; + static int32 &MaximumWantedLevel; + +public: + bool AreSwatRequired(); + bool AreFbiRequired(); + bool AreArmyRequired(); + int NumOfHelisRequired(); + void SetWantedLevel(int32); + void ClearQdCrimes(); + void UpdateWantedLevel(); +}; + +static_assert(sizeof(CWanted) == 0x204, "CWanted: error"); diff --git a/src/core/World.cpp b/src/core/World.cpp new file mode 100644 index 00000000..538e15c5 --- /dev/null +++ b/src/core/World.cpp @@ -0,0 +1,718 @@ +#include "common.h" +#include "patcher.h" +#include "Entity.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Vehicle.h" +#include "Object.h" +#include "Camera.h" +#include "DMAudio.h" +#include "CarCtrl.h" +#include "Garages.h" +#include "TempColModels.h" +#include "World.h" + +CPtrList *CWorld::ms_bigBuildingsList = (CPtrList*)0x6FAB60; +CPtrList &CWorld::ms_listMovingEntityPtrs = *(CPtrList*)0x8F433C; +CSector (*CWorld::ms_aSectors)[NUMSECTORS_X] = (CSector (*)[NUMSECTORS_Y])0x665608; +uint16 &CWorld::ms_nCurrentScanCode = *(uint16*)0x95CC64; + +uint8 &CWorld::PlayerInFocus = *(uint8 *)0x95CD61; +CPlayerInfo *CWorld::Players = (CPlayerInfo *)0x9412F0; +bool &CWorld::bNoMoreCollisionTorque = *(bool*)0x95CDCC; +CEntity *&CWorld::pIgnoreEntity = *(CEntity**)0x8F6494; +bool &CWorld::bIncludeDeadPeds = *(bool*)0x95CD8F; +bool &CWorld::bSecondShift = *(bool*)0x95CD54; +bool &CWorld::bForceProcessControl = *(bool*)0x95CD6C; +bool &CWorld::bProcessCutsceneOnly = *(bool*)0x95CD8B; + +void +CWorld::Add(CEntity *ent) +{ + if(ent->IsVehicle() || ent->IsPed()) + DMAudio.SetEntityStatus(((CPhysical*)ent)->m_audioEntityId, true); + + if(ent->bIsBIGBuilding) + ms_bigBuildingsList[ent->m_level].InsertItem(ent); + else + ent->Add(); + + if(ent->IsBuilding() || ent->IsDummy()) + return; + + if(!ent->bIsStatic) + ((CPhysical*)ent)->AddToMovingList(); +} + +void +CWorld::Remove(CEntity *ent) +{ + if(ent->IsVehicle() || ent->IsPed()) + DMAudio.SetEntityStatus(((CPhysical*)ent)->m_audioEntityId, false); + + if(ent->bIsBIGBuilding) + ms_bigBuildingsList[ent->m_level].RemoveItem(ent); + else + ent->Remove(); + + if(ent->IsBuilding() || ent->IsDummy()) + return; + + if(!ent->bIsStatic) + ((CPhysical*)ent)->RemoveFromMovingList(); +} + +void +CWorld::ClearScanCodes(void) +{ + CPtrNode *node; + for(int i = 0; i < NUMSECTORS_Y; i++) + for(int j = 0; j < NUMSECTORS_X; j++){ + CSector *s = &ms_aSectors[i][j]; + for(node = s->m_lists[ENTITYLIST_BUILDINGS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_VEHICLES].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_PEDS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_OBJECTS].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_DUMMIES].first; node; node = node->next) + ((CEntity*)node->item)->m_scanCode = 0; + } +} + +bool +CWorld::CameraToIgnoreThisObject(CEntity *ent) +{ + if(CGarages::IsModelIndexADoor(ent->GetModelIndex())) + return false; + return ((CObject*)ent)->m_bCameraToAvoidThisObject != 1; +} + +bool +CWorld::ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + int x, xstart, xend; + int y, ystart, yend; + int y1, y2; + float dist; + + AdvanceCurrentScanCode(); + + entity = nil; + dist = 1.0f; + + xstart = GetSectorIndexX(point1.x); + ystart = GetSectorIndexX(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexX(point2.y); + +#define LOSARGS CColLine(point1, point2), point, dist, entity, checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects + + if(xstart == xend && ystart == yend){ + // Only one sector + return ProcessLineOfSightSector(*GetSector(xstart, ystart), LOSARGS); + }else if(xstart == xend){ + // Only step in y + if(ystart < yend) + for(y = ystart; y <= yend; y++) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = ystart; y >= yend; y--) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + return dist < 1.0f; + }else if(ystart == yend){ + // Only step in x + if(xstart < xend) + for(x = xstart; x <= xend; x++) + ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS); + else + for(x = xstart; x >= xend; x--) + ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS); + return dist < 1.0f; + }else{ + if(point1.x < point2.x){ + // Step from left to right + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart+1) - point1.x)*m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + + for(x = xstart+1; x < xend; x++){ + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x+1) - point1.x)*m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + } + + y1 = y2; + y2 = yend; + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + }else{ + // Step from right to left + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x)*m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + + for(x = xstart-1; x > xend; x--){ + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x) - point1.x)*m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + } + + y1 = y2; + y2 = yend; + if(y1 < y2) + for(y = y1; y <= y2; y++) + ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + else + for(y = y1; y >= y2; y--) + ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + } + return dist < 1.0f; + } + +#undef LOSARGS +} + +bool +CWorld::ProcessLineOfSightSector(CSector §or, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + float mindist = dist; + bool deadPeds = !!bIncludeDeadPeds; + bIncludeDeadPeds = false; + + if(checkBuildings){ + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough); + } + + if(checkVehicles){ + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough); + } + + if(checkPeds){ + if(deadPeds) + bIncludeDeadPeds = true; + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough); + bIncludeDeadPeds = false; + } + + if(checkObjects){ + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, ignoreSeeThrough, ignoreSomeObjects); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, ignoreSomeObjects); + } + + if(checkDummies){ + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough); + } + + bIncludeDeadPeds = deadPeds; + + if(mindist < dist){ + dist = mindist; + return true; + }else + return false; +} + +bool +CWorld::ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + bool deadPeds = false; + float mindist = dist; + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + if(list.first && bIncludeDeadPeds && ((CEntity*)list.first->item)->IsPed()) + deadPeds = true; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(e->m_scanCode != GetCurrentScanCode() && + e != pIgnoreEntity && + (e->bUsesCollision || deadPeds) && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))){ + colmodel = nil; + e->m_scanCode = GetCurrentScanCode(); + + if(e->IsPed()){ + if(e->bUsesCollision || + deadPeds && ((CPed*)e)->m_nPedState == PED_DEAD){ + if(((CPed*)e)->UseGroundColModel()) + colmodel = &CTempColModels::ms_colModelPedGroundHit; + else + colmodel = ((CPedModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()))->GetHitColModel(); + }else + colmodel = nil; + }else if(e->bUsesCollision) + colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel(); + + if(colmodel && + CCollision::ProcessLineOfSight(line, e->GetMatrix(), *colmodel, point, dist, ignoreSeeThrough)) + entity = e; + } + } + + if(mindist < dist){ + dist = mindist; + return true; + }else + return false; +} + +bool +CWorld::ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + AdvanceCurrentScanCode(); + CVector point2(point1.x, point1.y, z2); + return CWorld::ProcessVerticalLineSector(*GetSector(GetSectorIndexX(point1.x), GetSectorIndexX(point1.y)), + CColLine(point1, point2), point, entity, + checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, poly); +} + +bool +CWorld::ProcessVerticalLineSector(CSector §or, const CColLine &line, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + float mindist = 1.0f; + + if(checkBuildings){ + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly); + } + + if(checkVehicles){ + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly); + } + + if(checkPeds){ + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly); + } + + if(checkObjects){ + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly); + } + + if(checkDummies){ + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, ignoreSeeThrough, poly); + } + + return mindist < 1.0f; +} + +bool +CWorld::ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + float mindist = dist; + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(e->m_scanCode != GetCurrentScanCode() && + e->bUsesCollision){ + e->m_scanCode = GetCurrentScanCode(); + + colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel(); + if(CCollision::ProcessVerticalLine(line, e->GetMatrix(), *colmodel, point, dist, ignoreSeeThrough, poly)) + entity = e; + } + } + + if(mindist < dist){ + dist = mindist; + return true; + }else + return false; +} + +bool +CWorld::GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + int x, xstart, xend; + int y, ystart, yend; + int y1, y2; + + AdvanceCurrentScanCode(); + + xstart = GetSectorIndexX(point1.x); + ystart = GetSectorIndexX(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexX(point2.y); + +#define LOSARGS CColLine(point1, point2), checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects + + if(xstart == xend && ystart == yend){ + // Only one sector + return GetIsLineOfSightSectorClear(*GetSector(xstart, ystart), LOSARGS); + }else if(xstart == xend){ + // Only step in y + if(ystart < yend){ + for(y = ystart; y <= yend; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + }else{ + for(y = ystart; y >= yend; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + } + }else if(ystart == yend){ + // Only step in x + if(xstart < xend){ + for(x = xstart; x <= xend; x++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS)) + return false; + }else{ + for(x = xstart; x >= xend; x--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS)) + return false; + } + }else{ + if(point1.x < point2.x){ + // Step from left to right + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart+1) - point1.x)*m + point1.y); + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + } + + for(x = xstart+1; x < xend; x++){ + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x+1) - point1.x)*m + point1.y); + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } + } + + y1 = y2; + y2 = yend; + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) + return false; + } + }else{ + // Step from right to left + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x)*m + point1.y); + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) + return false; + } + + for(x = xstart-1; x > xend; x--){ + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x) - point1.x)*m + point1.y); + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } + } + + y1 = y2; + y2 = yend; + if(y1 < y2){ + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) + return false; + }else{ + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) + return false; + } + } + } + + return true; + +#undef LOSARGS +} + +bool +CWorld::GetIsLineOfSightSectorClear(CSector §or, const CColLine &line, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + if(checkBuildings){ + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + if(checkVehicles){ + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + if(checkPeds){ + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + if(checkObjects){ + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS], line, ignoreSeeThrough, ignoreSomeObjects)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, ignoreSeeThrough, ignoreSomeObjects)) + return false; + } + + if(checkDummies){ + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + return true; +} + +bool +CWorld::GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(e->m_scanCode != GetCurrentScanCode() && + e->bUsesCollision){ + + e->m_scanCode = GetCurrentScanCode(); + + if(e != pIgnoreEntity && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))){ + + colmodel = CModelInfo::GetModelInfo(e->GetModelIndex())->GetColModel(); + + if(CCollision::TestLineOfSight(line, e->GetMatrix(), *colmodel, ignoreSeeThrough)) + return false; + } + } + } + + return true; +} + +float +CWorld::FindGroundZForCoord(float x, float y) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, 1000.0f), -1000.0f, point, ent, true, false, false, false, true, false, nil)) + return point.point.z; + else + return 20.0f; +} + +float +CWorld::FindGroundZFor3DCoord(float x, float y, float z, bool *found) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, z), -1000.0f, point, ent, true, false, false, false, false, false, nil)){ + if(found) + *found = true; + return point.point.z; + }else{ + if(found) + *found = false; + return 0.0f; + } +} + +float +CWorld::FindRoofZFor3DCoord(float x, float y, float z, bool *found) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, z), 1000.0f, point, ent, true, false, false, false, true, false, nil)){ + if(found) + *found = true; + return point.point.z; + }else{ + if(found == nil) + printf("THERE IS NO MAP BELOW THE FOLLOWING COORS:%f %f %f. (FindGroundZFor3DCoord)\n", x, y, z); + if(found) + *found = false; + return 20.0f; + } +} + +CPlayerPed* +FindPlayerPed(void) +{ + return CWorld::Players[CWorld::PlayerInFocus].m_pPed; +} + +CVehicle* +FindPlayerVehicle(void) +{ + CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(ped->bInVehicle && ped->m_pMyVehicle) + return ped->m_pMyVehicle; + else + return nil; +} + +CVehicle* +FindPlayerTrain(void) +{ + if(FindPlayerVehicle() && FindPlayerVehicle()->IsTrain()) + return FindPlayerVehicle(); + else + return nil; +} + +CEntity* +FindPlayerEntity(void) +{ + CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(ped->bInVehicle && ped->m_pMyVehicle) + return ped->m_pMyVehicle; + else + return ped; +} + +CVector +FindPlayerCoors(void) +{ + CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(ped->bInVehicle && ped->m_pMyVehicle) + return ped->m_pMyVehicle->GetPosition(); + else + return ped->GetPosition(); +} + +CVector& +FindPlayerSpeed(void) +{ + CPlayerPed *ped = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(ped->bInVehicle && ped->m_pMyVehicle) + return ped->m_pMyVehicle->m_vecMoveSpeed; + else + return ped->m_vecMoveSpeed; +} + +CVector& +FindPlayerCentreOfWorld(int32 player) +{ + if(CCarCtrl::bCarsGeneratedAroundCamera) + return TheCamera.GetPosition(); + if(CWorld::Players[player].m_pRemoteVehicle) + return CWorld::Players[player].m_pRemoteVehicle->GetPosition(); + if(FindPlayerVehicle()) + return FindPlayerVehicle()->GetPosition(); + return CWorld::Players[player].m_pPed->GetPosition(); +} + +CVector& +FindPlayerCentreOfWorld_NoSniperShift(void) +{ + if(CCarCtrl::bCarsGeneratedAroundCamera) + return TheCamera.GetPosition(); + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle) + return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetPosition(); + if(FindPlayerVehicle()) + return FindPlayerVehicle()->GetPosition(); + return CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetPosition(); +} + +float +FindPlayerHeading(void) +{ + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle) + return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetForward().Heading(); + if(FindPlayerVehicle()) + return FindPlayerVehicle()->GetForward().Heading(); + return CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetForward().Heading(); +} + +STARTPATCHES + InjectHook(0x4AE930, CWorld::Add, PATCH_JUMP); + InjectHook(0x4AE9D0, CWorld::Remove, PATCH_JUMP); + InjectHook(0x4B1F60, CWorld::ClearScanCodes, PATCH_JUMP); + InjectHook(0x4AF970, CWorld::ProcessLineOfSight, PATCH_JUMP); + InjectHook(0x4B0A80, CWorld::ProcessLineOfSightSector, PATCH_JUMP); + InjectHook(0x4B0C70, CWorld::ProcessLineOfSightSectorList, PATCH_JUMP); + InjectHook(0x4B0DE0, CWorld::ProcessVerticalLine, PATCH_JUMP); + InjectHook(0x4B0EF0, CWorld::ProcessVerticalLineSector, PATCH_JUMP); + InjectHook(0x4B1090, CWorld::ProcessVerticalLineSectorList, PATCH_JUMP); + InjectHook(0x4AEAA0, CWorld::GetIsLineOfSightClear, PATCH_JUMP); + InjectHook(0x4B2000, CWorld::GetIsLineOfSightSectorClear, PATCH_JUMP); + InjectHook(0x4B2160, CWorld::GetIsLineOfSightSectorListClear, PATCH_JUMP); + + InjectHook(0x4B3A80, CWorld::FindGroundZForCoord, PATCH_JUMP); + InjectHook(0x4B3AE0, CWorld::FindGroundZFor3DCoord, PATCH_JUMP); + InjectHook(0x4B3B50, CWorld::FindRoofZFor3DCoord, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/World.h b/src/core/World.h new file mode 100644 index 00000000..3b7090da --- /dev/null +++ b/src/core/World.h @@ -0,0 +1,119 @@ +#pragma once + +#include "Game.h" +#include "Lists.h" +#include "PlayerInfo.h" + +/* Sectors span from -2000 to 2000 in x and y. + * With 100x100 sectors, each is 40x40 units. */ + +#define SECTOR_SIZE_X (40.0f) +#define SECTOR_SIZE_Y (40.0f) + +#define NUMSECTORS_X (100) +#define NUMSECTORS_Y (100) + +#define WORLD_SIZE_X (NUMSECTORS_X * SECTOR_SIZE_X) +#define WORLD_SIZE_Y (NUMSECTORS_Y * SECTOR_SIZE_Y) + +#define WORLD_MIN_X (-2000.0f) +#define WORLD_MIN_Y (-2000.0f) + +#define WORLD_MAX_X (WORLD_MIN_X + WORLD_SIZE_X) +#define WORLD_MAX_Y (WORLD_MIN_Y + WORLD_SIZE_Y) + +enum +{ + ENTITYLIST_BUILDINGS, + ENTITYLIST_BUILDINGS_OVERLAP, + ENTITYLIST_OBJECTS, + ENTITYLIST_OBJECTS_OVERLAP, + ENTITYLIST_VEHICLES, + ENTITYLIST_VEHICLES_OVERLAP, + ENTITYLIST_PEDS, + ENTITYLIST_PEDS_OVERLAP, + ENTITYLIST_DUMMIES, + ENTITYLIST_DUMMIES_OVERLAP, + + NUMSECTORENTITYLISTS +}; + +class CSector +{ +public: + CPtrList m_lists[NUMSECTORENTITYLISTS]; +}; +static_assert(sizeof(CSector) == 0x28, "CSector: error"); + +class CEntity; +struct CColPoint; +struct CColLine; +struct CStoredCollPoly; + +class CWorld +{ + static CPtrList *ms_bigBuildingsList; // [4]; + static CPtrList &ms_listMovingEntityPtrs; + static CSector (*ms_aSectors)[NUMSECTORS_X]; // [NUMSECTORS_Y][NUMSECTORS_X]; + static uint16 &ms_nCurrentScanCode; + +public: + static uint8 &PlayerInFocus; + static CPlayerInfo *Players; + static CEntity *&pIgnoreEntity; + static bool &bIncludeDeadPeds; + static bool &bNoMoreCollisionTorque; + static bool &bSecondShift; + static bool &bForceProcessControl; + static bool &bProcessCutsceneOnly; + + static void Remove(CEntity *entity); + static void Add(CEntity *entity); + + static CSector *GetSector(int x, int y) { return &ms_aSectors[y][x]; } + static CPtrList &GetBigBuildingList(eLevelName i) { return ms_bigBuildingsList[i]; } + static CPtrList &GetMovingEntityList(void) { return ms_listMovingEntityPtrs; } + static uint16 GetCurrentScanCode(void) { return ms_nCurrentScanCode; } + static void AdvanceCurrentScanCode(void){ + if(++CWorld::ms_nCurrentScanCode == 0){ + CWorld::ClearScanCodes(); + CWorld::ms_nCurrentScanCode = 1; + } + } + static void ClearScanCodes(void); + + static bool CameraToIgnoreThisObject(CEntity *ent); + + static bool ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessLineOfSightSector(CSector §or, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool ProcessVerticalLineSector(CSector §or, const CColLine &line, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool GetIsLineOfSightSectorClear(CSector §or, const CColLine &line, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + + static float FindGroundZForCoord(float x, float y); + static float FindGroundZFor3DCoord(float x, float y, float z, bool *found); + static float FindRoofZFor3DCoord(float x, float y, float z, bool *found); + + static float GetSectorX(float f) { return ((f - WORLD_MIN_X)/SECTOR_SIZE_X); } + static float GetSectorY(float f) { return ((f - WORLD_MIN_Y)/SECTOR_SIZE_Y); } + static int GetSectorIndexX(float f) { return (int)GetSectorX(f); } + static int GetSectorIndexY(float f) { return (int)GetSectorY(f); } + static float GetWorldX(int x) { return x*SECTOR_SIZE_X + WORLD_MIN_X; } + static float GetWorldY(int y) { return y*SECTOR_SIZE_Y + WORLD_MIN_Y; } +}; + +class CPlayerPed; +class CVehicle; +CPlayerPed *FindPlayerPed(void); +CVehicle *FindPlayerVehicle(void); +CVehicle *FindPlayerTrain(void); +CEntity *FindPlayerEntity(void); +CVector FindPlayerCoors(void); +CVector &FindPlayerSpeed(void); +CVector &FindPlayerCentreOfWorld(int32 player); +CVector &FindPlayerCentreOfWorld_NoSniperShift(void); +float FindPlayerHeading(void); diff --git a/src/core/ZoneCull.cpp b/src/core/ZoneCull.cpp new file mode 100644 index 00000000..90155bcf --- /dev/null +++ b/src/core/ZoneCull.cpp @@ -0,0 +1,370 @@ +#include "common.h" +#include "patcher.h" +#include "Building.h" +#include "Treadable.h" +#include "Train.h" +#include "Pools.h" +#include "Timer.h" +#include "Camera.h" +#include "World.h" +#include "FileMgr.h" +#include "ZoneCull.h" + +int32 &CCullZones::NumCullZones = *(int*)0x8F2564; +CCullZone *CCullZones::aZones = (CCullZone*)0x864750; // [NUMCULLZONES]; +int32 &CCullZones::NumAttributeZones = *(int*)0x8E29D0; +CAttributeZone *CCullZones::aAttributeZones = (CAttributeZone*)0x709C60; // [NUMATTRIBZONES]; +uint16 *CCullZones::aIndices = (uint16*)0x847330; // [NUMZONEINDICES]; +int16 *CCullZones::aPointersToBigBuildingsForBuildings = (int16*)0x86C9D0; // [NUMBUILDINGS]; +int16 *CCullZones::aPointersToBigBuildingsForTreadables = (int16*)0x8F1B8C; // [NUMTREADABLES]; + +int32 &CCullZones::CurrentWantedLevelDrop_Player = *(int32*)0x880DA8; +int32 &CCullZones::CurrentFlags_Camera = *(int32*)0x940718; +int32 &CCullZones::CurrentFlags_Player = *(int32*)0x9415F0; +int32 &CCullZones::OldCullZone = *(int32*)0x8E2C90; +int32 &CCullZones::EntityIndicesUsed = *(int32*)0x8F2508; +bool &CCullZones::bCurrentSubwayIsInvisible = *(bool*)0x95CDA5; +bool &CCullZones::bCullZonesDisabled = *(bool*)0x95CD4A; + + +void +CCullZones::Init(void) +{ + int i; + + NumAttributeZones = 0; + NumCullZones = 0; + CurrentWantedLevelDrop_Player = 0; + CurrentFlags_Camera = 0; + CurrentFlags_Player = 0; + OldCullZone = -1; + EntityIndicesUsed = 0; + bCurrentSubwayIsInvisible = false; + + for(i = 0; i < NUMBUILDINGS; i++) + aPointersToBigBuildingsForBuildings[i] = -1; + for(i = 0; i < NUMTREADABLES; i++) + aPointersToBigBuildingsForTreadables[i] = -1; +} + +void +CCullZones::ResolveVisibilities(void) +{ + int fd; + + CFileMgr::SetDir(""); + fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb"); + if(fd > 0){ + CFileMgr::Read(fd, (char*)&NumCullZones, 4); + CFileMgr::Read(fd, (char*)aZones, NUMCULLZONES*sizeof(CCullZone)); + CFileMgr::Read(fd, (char*)&NumAttributeZones, 4); + CFileMgr::Read(fd, (char*)aAttributeZones, NUMATTRIBZONES*sizeof(CAttributeZone)); + CFileMgr::Read(fd, (char*)aIndices, NUMZONEINDICES*2); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, NUMBUILDINGS*2); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, NUMTREADABLES*2); + CFileMgr::CloseFile(fd); + }else{ + // TODO: implement code from mobile to generate data here + } +} + +void +CCullZones::Update(void) +{ + bool invisible; + + if(bCullZonesDisabled) + return; + + switch(CTimer::GetFrameCounter() & 7){ + case 0: + case 4: + /* Update Cull zone */ + ForceCullZoneCoors(TheCamera.GetGameCamPosition()); + break; + + case 2: + /* Update camera attributes */ + CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil); + invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0; + if(invisible != bCurrentSubwayIsInvisible){ + MarkSubwayAsInvisible(!invisible); + bCurrentSubwayIsInvisible = invisible; + } + break; + + case 6: + /* Update player attributes */ + CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(), + &CurrentWantedLevelDrop_Player); + break; + } +} + +void +CCullZones::ForceCullZoneCoors(CVector coors) +{ + int32 z; + z = FindCullZoneForCoors(coors); + if(z != OldCullZone){ + if(OldCullZone >= 0) + aZones[OldCullZone].DoStuffLeavingZone(); + if(z >= 0) + aZones[z].DoStuffEnteringZone(); + OldCullZone = z; + } +} + +int32 +CCullZones::FindCullZoneForCoors(CVector coors) +{ + int i; + + for(i = 0; i < NumCullZones; i++) + if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx && + coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy && + coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz) + return i; + return -1; +} + +int32 +CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel) +{ + int i; + int32 attribs; + + attribs = 0; + for(i = 0; i < NumAttributeZones; i++) + if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){ + attribs |= aAttributeZones[i].attributes; + if(wantedLevel && *wantedLevel <= aAttributeZones[i].wantedLevel) + *wantedLevel = aAttributeZones[i].wantedLevel; + } + return attribs; +} + +CAttributeZone* +CCullZones::FindZoneWithStairsAttributeForPlayer(void) +{ + int i; + CVector coors; + + coors = FindPlayerCoors(); + for(i = 0; i < NumAttributeZones; i++) + if(aAttributeZones[i].attributes & ATTRZONE_STAIRS && + coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz) + return &aAttributeZones[i]; + return nil; +} + +void +CCullZones::MarkSubwayAsInvisible(bool visible) +{ + int i, n; + CEntity *e; + CVehicle *v; + + n = CPools::GetBuildingPool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetTreadablePool()->GetSize(); + for(i = 0; i < n; i++){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetVehiclePool()->GetSize(); + for(i = 0; i < n; i++){ + v = CPools::GetVehiclePool()->GetSlot(i); + if(v && v->IsTrain() && ((CTrain*)v)->m_trackId != 0) + v->bIsVisible = visible; + } +} + +void +CCullZones::AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel) +{ + CCullZone *cull; + CAttributeZone *attrib; + + CVector v; + if((flag & ATTRZONE_NOTCULLZONE) == 0){ + cull = &aZones[NumCullZones++]; + v = position; + // WTF is this? + if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f) + v = CVector(1061.7f, -613.0f, 19.0f); + if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f) + v = CVector(1061.4f, -506.0f, 18.5f); + cull->position.x = clamp(v.x, minx, maxx); + cull->position.y = clamp(v.y, miny, maxy); + cull->position.z = clamp(v.z, minz, maxz); + cull->minx = minx; + cull->maxx = maxx; + cull->miny = miny; + cull->maxy = maxy; + cull->minz = minz; + cull->maxz = maxz; + cull->unk2 = 0; + cull->unk3 = 0; + cull->unk4 = 0; + cull->m_indexStart = 0; + } + if(flag & ~ATTRZONE_NOTCULLZONE){ + attrib = &aAttributeZones[NumAttributeZones++]; + attrib->minx = minx; + attrib->maxx = maxx; + attrib->miny = miny; + attrib->maxy = maxy; + attrib->minz = minz; + attrib->maxz = maxz; + attrib->attributes = flag; + attrib->wantedLevel = wantedLevel; + } +} + + + +void +CCullZone::DoStuffLeavingZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = false; + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = false; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m; i++) + DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++) + DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;; + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = true;; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i) +{ + int16 bb; + int j; + + if(i < 6000){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= 6000; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +STARTPATCHES + InjectHook(0x524BC0, &CCullZones::Init, PATCH_JUMP); + InjectHook(0x524EC0, &CCullZones::ResolveVisibilities, PATCH_JUMP); + InjectHook(0x524F80, &CCullZones::Update, PATCH_JUMP); + InjectHook(0x525370, &CCullZones::AddCullZone, PATCH_JUMP); + InjectHook(0x5250D0, &CCullZones::ForceCullZoneCoors, PATCH_JUMP); + InjectHook(0x525130, &CCullZones::FindCullZoneForCoors, PATCH_JUMP); + InjectHook(0x5251C0, &CCullZones::FindAttributesForCoors, PATCH_JUMP); + InjectHook(0x525290, &CCullZones::FindZoneWithStairsAttributeForPlayer, PATCH_JUMP); + + InjectHook(0x525610, &CCullZone::DoStuffLeavingZone, PATCH_JUMP); + InjectHook(0x525810, &CCullZone::DoStuffEnteringZone, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/ZoneCull.h b/src/core/ZoneCull.h new file mode 100644 index 00000000..5b04b4f9 --- /dev/null +++ b/src/core/ZoneCull.h @@ -0,0 +1,94 @@ +class CCullZone +{ +public: + CVector position; + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + + // TODO: figure these out: + int32 m_indexStart; + int16 unk2; + int16 unk3; + int16 unk4; + int16 m_numBuildings; + int16 m_numTreadablesPlus10m; + int16 m_numTreadables; + + void DoStuffLeavingZone(void); + static void DoStuffLeavingZone_OneBuilding(uint16 i); + static void DoStuffLeavingZone_OneTreadableBoth(uint16 i); + void DoStuffEnteringZone(void); + static void DoStuffEnteringZone_OneBuilding(uint16 i); + static void DoStuffEnteringZone_OneTreadablePlus10m(uint16 i); + static void DoStuffEnteringZone_OneTreadable(uint16 i); +}; + +enum eZoneAttribs +{ + ATTRZONE_CAMCLOSEIN = 1, + ATTRZONE_STAIRS = 2, + ATTRZONE_1STPERSON = 4, + ATTRZONE_NORAIN = 8, + ATTRZONE_NOPOLICE = 0x10, + ATTRZONE_NOTCULLZONE = 0x20, + ATTRZONE_DOINEEDCOLLISION = 0x40, + ATTRZONE_SUBWAYVISIBLE = 0x80, +}; + +struct CAttributeZone +{ + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + int16 attributes; + int16 wantedLevel; +}; + +class CCullZones +{ +public: + static int32 &NumCullZones; + static CCullZone *aZones; // [NUMCULLZONES]; + static int32 &NumAttributeZones; + static CAttributeZone *aAttributeZones; // [NUMATTRIBZONES]; + static uint16 *aIndices; // [NUMZONEINDICES]; + static int16 *aPointersToBigBuildingsForBuildings; // [NUMBUILDINGS]; + static int16 *aPointersToBigBuildingsForTreadables; // [NUMTREADABLES]; + + static int32 &CurrentWantedLevelDrop_Player; + static int32 &CurrentFlags_Camera; + static int32 &CurrentFlags_Player; + static int32 &OldCullZone; + static int32 &EntityIndicesUsed; + static bool &bCurrentSubwayIsInvisible; + static bool &bCullZonesDisabled; + + static void Init(void); + static void ResolveVisibilities(void); + static void Update(void); + static void ForceCullZoneCoors(CVector coors); + static int32 FindCullZoneForCoors(CVector coors); + static int32 FindAttributesForCoors(CVector coors, int32 *wantedLevel); + static CAttributeZone *FindZoneWithStairsAttributeForPlayer(void); + static void MarkSubwayAsInvisible(bool visible); + static void AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel); + static bool CamCloseInForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_CAMCLOSEIN) != 0; } + static bool CamStairsForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_STAIRS) != 0; } + static bool Cam1stPersonForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_1STPERSON) != 0; } + static bool NoPolice(void) { return (CurrentFlags_Player & ATTRZONE_NOPOLICE) != 0; } + static bool DoINeedToLoadCollision(void) { return (CurrentFlags_Player & ATTRZONE_DOINEEDCOLLISION) != 0; } + static bool PlayerNoRain(void) { return (CurrentFlags_Player & ATTRZONE_NORAIN) != 0; } + static bool CamNoRain(void) { return (CurrentFlags_Camera & ATTRZONE_NORAIN) != 0; } + static int32 GetWantedLevelDrop(void) { return CurrentWantedLevelDrop_Player; } +}; diff --git a/src/core/Zones.cpp b/src/core/Zones.cpp new file mode 100644 index 00000000..363fc3d9 --- /dev/null +++ b/src/core/Zones.cpp @@ -0,0 +1,874 @@ +#include "common.h" +#include "patcher.h" + +#include "Zones.h" + +#include "Clock.h" +#include "Text.h" +#include "World.h" + +eLevelName &CTheZones::m_CurrLevel = *(eLevelName*)0x8F2BC8; +CZone *&CTheZones::m_pPlayersZone = *(CZone**)0x8F254C; +int16 &CTheZones::FindIndex = *(int16*)0x95CC40; + +uint16 &CTheZones::NumberOfAudioZones = *(uint16*)0x95CC84; +int16 *CTheZones::AudioZoneArray = (int16*)0x713BC0; +uint16 &CTheZones::TotalNumberOfMapZones = *(uint16*)0x95CC74; +uint16 &CTheZones::TotalNumberOfZones = *(uint16*)0x95CC36; +CZone *CTheZones::ZoneArray = (CZone*)0x86BEE0; +CZone *CTheZones::MapZoneArray = (CZone*)0x663EC0; +uint16 &CTheZones::TotalNumberOfZoneInfos = *(uint16*)0x95CC3C; +CZoneInfo *CTheZones::ZoneInfoArray = (CZoneInfo*)0x714400; + +#define SWAPF(a, b) { float t; t = a; a = b; b = t; } + +static void +CheckZoneInfo(CZoneInfo *info) +{ + assert(info->carThreshold[0] >= 0); + assert(info->carThreshold[0] <= info->carThreshold[1]); + assert(info->carThreshold[1] <= info->carThreshold[2]); + assert(info->carThreshold[2] <= info->carThreshold[3]); + assert(info->carThreshold[3] <= info->carThreshold[4]); + assert(info->carThreshold[4] <= info->carThreshold[5]); + assert(info->carThreshold[5] <= info->copThreshold); + assert(info->copThreshold <= info->gangThreshold[0]); + assert(info->gangThreshold[0] <= info->gangThreshold[1]); + assert(info->gangThreshold[1] <= info->gangThreshold[2]); + assert(info->gangThreshold[2] <= info->gangThreshold[3]); + assert(info->gangThreshold[3] <= info->gangThreshold[4]); + assert(info->gangThreshold[4] <= info->gangThreshold[5]); + assert(info->gangThreshold[5] <= info->gangThreshold[6]); + assert(info->gangThreshold[6] <= info->gangThreshold[7]); + assert(info->gangThreshold[7] <= info->gangThreshold[8]); +} + +wchar* +CZone::GetTranslatedName(void) +{ + return TheText.Get(name); +} + +void +CTheZones::Init(void) +{ + int i; + for(i = 0; i < NUMAUDIOZONES; i++) + AudioZoneArray[i] = -1; + NumberOfAudioZones = 0; + + CZoneInfo *zonei; + int x = 1000/6; + for(i = 0; i < 2*NUMZONES; i++){ + zonei = &ZoneInfoArray[i]; + zonei->carDensity = 10; + zonei->carThreshold[0] = x; + zonei->carThreshold[1] = zonei->carThreshold[0] + x; + zonei->carThreshold[2] = zonei->carThreshold[1] + x; + zonei->carThreshold[3] = zonei->carThreshold[2] + x; + zonei->carThreshold[4] = zonei->carThreshold[3]; + zonei->carThreshold[5] = zonei->carThreshold[4]; + zonei->copThreshold = zonei->carThreshold[5] + x; + zonei->gangThreshold[0] = zonei->copThreshold; + zonei->gangThreshold[1] = zonei->gangThreshold[0]; + zonei->gangThreshold[2] = zonei->gangThreshold[1]; + zonei->gangThreshold[3] = zonei->gangThreshold[2]; + zonei->gangThreshold[4] = zonei->gangThreshold[3]; + zonei->gangThreshold[5] = zonei->gangThreshold[4]; + zonei->gangThreshold[6] = zonei->gangThreshold[5]; + zonei->gangThreshold[7] = zonei->gangThreshold[6]; + zonei->gangThreshold[8] = zonei->gangThreshold[7]; + CheckZoneInfo(zonei); + } + TotalNumberOfZoneInfos = 1; // why 1? + + for(i = 0; i < NUMZONES; i++) + memset(&ZoneArray[i], 0, sizeof(CZone)); + strcpy(ZoneArray[0].name, "CITYZON"); + ZoneArray[0].minx = -4000.0f; + ZoneArray[0].miny = -4000.0f; + ZoneArray[0].minz = -500.0f; + ZoneArray[0].maxx = 4000.0f; + ZoneArray[0].maxy = 4000.0f; + ZoneArray[0].maxz = 500.0f; + ZoneArray[0].level = LEVEL_NONE; + TotalNumberOfZones = 1; + + m_CurrLevel = LEVEL_NONE; + m_pPlayersZone = &ZoneArray[0]; + + for(i = 0; i < NUMMAPZONES; i++){ + memset(&MapZoneArray[i], 0, sizeof(CZone)); + MapZoneArray[i].type = ZONE_MAPZONE; + } + strcpy(MapZoneArray[0].name, "THEMAP"); + MapZoneArray[0].minx = -4000.0f; + MapZoneArray[0].miny = -4000.0f; + MapZoneArray[0].minz = -500.0f; + MapZoneArray[0].maxx = 4000.0f; + MapZoneArray[0].maxy = 4000.0f; + MapZoneArray[0].maxz = 500.0f; + MapZoneArray[0].level = LEVEL_NONE; + TotalNumberOfMapZones = 1; +} + +void +CTheZones::Update(void) +{ + CVector pos; + pos = FindPlayerCoors(); + m_pPlayersZone = FindSmallestZonePosition(&pos); + m_CurrLevel = GetLevelFromPosition(pos); +} + +void +CTheZones::CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + CZone *zone; + char *p; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + zone = &ZoneArray[TotalNumberOfZones++]; + strncpy(zone->name, name, 7); + zone->name[7] = '\0'; + zone->type = type; + zone->minx = minx; + zone->miny = miny; + zone->minz = minz; + zone->maxx = maxx; + zone->maxy = maxy; + zone->maxz = maxz; + zone->level = level; + if(type == ZONE_AUDIO || type == ZONE_TYPE1 || type == ZONE_TYPE2){ + zone->zoneinfoDay = TotalNumberOfZoneInfos++; + zone->zoneinfoNight = TotalNumberOfZoneInfos++; + } +} + +void +CTheZones::CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + CZone *zone; + char *p; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + zone = &MapZoneArray[TotalNumberOfMapZones++]; + strncpy(zone->name, name, 7); + zone->name[7] = '\0'; + zone->type = type; + zone->minx = minx; + zone->miny = miny; + zone->minz = minz; + zone->maxx = maxx; + zone->maxy = maxy; + zone->maxz = maxz; + zone->level = level; +} + +void +CTheZones::PostZoneCreation(void) +{ + int i; + for(i = 1; i < TotalNumberOfZones; i++) + InsertZoneIntoZoneHierarchy(&ZoneArray[i]); + InitialiseAudioZoneArray(); +} + +void +CTheZones::InsertZoneIntoZoneHierarchy(CZone *zone) +{ + zone->child = nil; + zone->parent = nil; + zone->next = nil; + InsertZoneIntoZoneHierRecursive(zone, &ZoneArray[0]); +} + +bool +CTheZones::InsertZoneIntoZoneHierRecursive(CZone *inner, CZone *outer) +{ + int n; + CZone *child, *next, *insert; + + // return false if inner was not inserted into outer + if(outer == nil || + !ZoneIsEntirelyContainedWithinOtherZone(inner, outer)) + return false; + + // try to insert inner into children of outer + for(child = outer->child; child; child = child->next) + if(InsertZoneIntoZoneHierRecursive(inner, child)) + return true; + + // insert inner as child of outer + // count number of outer's children contained within inner + n = 0; + for(child = outer->child; child; child = child->next) + if(ZoneIsEntirelyContainedWithinOtherZone(child, inner)) + n++; + inner->next = outer->child; + inner->parent = outer; + outer->child = inner; + // move children from outer to inner + if(n){ + insert = inner; + for(child = inner->next; child; child = next){ + next = child->next; + if(ZoneIsEntirelyContainedWithinOtherZone(child,inner)){ + insert->next = child->next; + child->parent = inner; + child->next = inner->child; + inner->child = child; + }else + insert = child; + } + } + + return true; +} + +bool +CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone *inner, CZone *outer) +{ + char tmp[100]; + + if(inner->minx < outer->minx || + inner->maxx > outer->maxx || + inner->miny < outer->miny || + inner->maxy > outer->maxy || + inner->minz < outer->minz || + inner->maxz > outer->maxz){ + CVector vmin(inner->minx, inner->miny, inner->minz); + if(PointLiesWithinZone(vmin, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + CVector vmax(inner->maxx, inner->maxy, inner->maxz); + if(PointLiesWithinZone(vmax, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + return false; + } + return true; +} + +bool +CTheZones::PointLiesWithinZone(const CVector &v, CZone *zone) +{ + return zone->minx <= v.x && v.x <= zone->maxx && + zone->miny <= v.y && v.y <= zone->maxy && + zone->minz <= v.z && v.z <= zone->maxz; +} + +eLevelName +CTheZones::GetLevelFromPosition(CVector const &v) +{ + int i; +// char tmp[116]; +// if(!PointLiesWithinZone(v, &MapZoneArray[0])) +// sprintf(tmp, "x = %.3f y= %.3f z = %.3f\n", v.x, v.y, v.z); + for(i = 1; i < TotalNumberOfMapZones; i++) + if(PointLiesWithinZone(v, &MapZoneArray[i])) + return MapZoneArray[i].level; + return MapZoneArray[0].level; +} + +CZone* +CTheZones::FindSmallestZonePosition(const CVector *v) +{ + CZone *best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionType(const CVector *v, eZoneType type) +{ + CZone *best = nil; + if(ZoneArray[0].type == type) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + if(zone->type == type) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionILN(const CVector *v) +{ + CZone *best = nil; + if(ZoneArray[0].type == ZONE_AUDIO || + ZoneArray[0].type == ZONE_TYPE1 || + ZoneArray[0].type == ZONE_TYPE2) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(*v, zone)){ + if(zone->type == ZONE_AUDIO || + zone->type == ZONE_TYPE1 || + zone->type == ZONE_TYPE2) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +int16 +CTheZones::FindZoneByLabelAndReturnIndex(char *name) +{ + char str[8]; + memset(str, 0, 8); + strncpy(str, name, 8); + for(FindIndex = 0; FindIndex < TotalNumberOfZones; FindIndex++) + if(strcmp(GetZone(FindIndex)->name, name) == 0) + return FindIndex; + return -1; +} + +CZoneInfo* +CTheZones::GetZoneInfo(const CVector *v, uint8 day) +{ + CZone *zone; + zone = FindSmallestZonePositionILN(v); + if(zone == nil) + return &ZoneInfoArray[0]; + return &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; +} + +void +CTheZones::GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info) +{ + CZoneInfo *day, *night; + float d, n; + + day = GetZoneInfo(pos, 1); + night = GetZoneInfo(pos, 0); + + if(CClock::GetIsTimeInRange(8, 19)) + *info = *day; + else if(CClock::GetIsTimeInRange(22, 5)) + *info = *night; + else{ + if(CClock::GetIsTimeInRange(19, 22)){ + n = (CClock::GetHours() - 19) / 3.0f; + assert(n >= 0.0f && n <= 1.0f); + d = 1.0f - n; + }else{ + d = (CClock::GetHours() - 5) / 3.0f; + assert(d >= 0.0f && d <= 1.0f); + n = 1.0f - d; + } + info->carDensity = day->carDensity * d + night->carDensity * n; + info->carThreshold[0] = day->carThreshold[0] * d + night->carThreshold[0] * n; + info->carThreshold[1] = day->carThreshold[1] * d + night->carThreshold[1] * n; + info->carThreshold[2] = day->carThreshold[2] * d + night->carThreshold[2] * n; + info->carThreshold[3] = day->carThreshold[3] * d + night->carThreshold[3] * n; + info->carThreshold[4] = day->carThreshold[4] * d + night->carThreshold[4] * n; + info->carThreshold[5] = day->carThreshold[5] * d + night->carThreshold[5] * n; + info->copThreshold = day->copThreshold * d + night->copThreshold * n; + info->gangThreshold[0] = day->gangThreshold[0] * d + night->gangThreshold[0] * n; + info->gangThreshold[1] = day->gangThreshold[1] * d + night->gangThreshold[1] * n; + info->gangThreshold[2] = day->gangThreshold[2] * d + night->gangThreshold[2] * n; + info->gangThreshold[3] = day->gangThreshold[3] * d + night->gangThreshold[3] * n; + info->gangThreshold[4] = day->gangThreshold[4] * d + night->gangThreshold[4] * n; + info->gangThreshold[5] = day->gangThreshold[5] * d + night->gangThreshold[5] * n; + info->gangThreshold[6] = day->gangThreshold[6] * d + night->gangThreshold[6] * n; + info->gangThreshold[7] = day->gangThreshold[7] * d + night->gangThreshold[7] * n; + info->gangThreshold[8] = day->gangThreshold[8] * d + night->gangThreshold[8] * n; + + info->pedDensity = day->pedDensity * d + night->pedDensity * n; + info->copDensity = day->copDensity * d + night->copDensity * n; + info->gangDensity[0] = day->gangDensity[0] * d + night->gangDensity[0] * n; + info->gangDensity[1] = day->gangDensity[1] * d + night->gangDensity[1] * n; + info->gangDensity[2] = day->gangDensity[2] * d + night->gangDensity[2] * n; + info->gangDensity[3] = day->gangDensity[3] * d + night->gangDensity[3] * n; + info->gangDensity[4] = day->gangDensity[4] * d + night->gangDensity[4] * n; + info->gangDensity[5] = day->gangDensity[5] * d + night->gangDensity[5] * n; + info->gangDensity[6] = day->gangDensity[6] * d + night->gangDensity[6] * n; + info->gangDensity[7] = day->gangDensity[7] * d + night->gangDensity[7] * n; + info->gangDensity[8] = day->gangDensity[8] * d + night->gangDensity[8] * n; + } + if(CClock::GetIsTimeInRange(5, 19)) + info->pedGroup = day->pedGroup; + else + info->pedGroup = night->pedGroup; + + CheckZoneInfo(info); +} + +void +CTheZones::SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + + CheckZoneInfo(info); + + if(carDensity != -1) info->carDensity = carDensity; + int16 oldCar1Num = info->carThreshold[1] - info->carThreshold[0]; + int16 oldCar2Num = info->carThreshold[2] - info->carThreshold[1]; + int16 oldCar3Num = info->carThreshold[3] - info->carThreshold[2]; + int16 oldCar4Num = info->carThreshold[4] - info->carThreshold[3]; + int16 oldCar5Num = info->carThreshold[5] - info->carThreshold[4]; + int16 oldCopNum = info->copThreshold - info->carThreshold[5]; + int16 oldGang0Num = info->gangThreshold[0] - info->copThreshold; + int16 oldGang1Num = info->gangThreshold[1] - info->gangThreshold[0]; + int16 oldGang2Num = info->gangThreshold[2] - info->gangThreshold[1]; + int16 oldGang3Num = info->gangThreshold[3] - info->gangThreshold[2]; + int16 oldGang4Num = info->gangThreshold[4] - info->gangThreshold[3]; + int16 oldGang5Num = info->gangThreshold[5] - info->gangThreshold[4]; + int16 oldGang6Num = info->gangThreshold[6] - info->gangThreshold[5]; + int16 oldGang7Num = info->gangThreshold[7] - info->gangThreshold[6]; + int16 oldGang8Num = info->gangThreshold[8] - info->gangThreshold[7]; + + if(car0Num != -1) info->carThreshold[0] = car0Num; + if(car1Num != -1) info->carThreshold[1] = info->carThreshold[0] + car1Num; + else info->carThreshold[1] = info->carThreshold[0] + oldCar1Num; + if(car2Num != -1) info->carThreshold[2] = info->carThreshold[1] + car2Num; + else info->carThreshold[2] = info->carThreshold[1] + oldCar2Num; + if(car3Num != -1) info->carThreshold[3] = info->carThreshold[2] + car3Num; + else info->carThreshold[3] = info->carThreshold[2] + oldCar3Num; + if(car4Num != -1) info->carThreshold[4] = info->carThreshold[3] + car4Num; + else info->carThreshold[4] = info->carThreshold[3] + oldCar4Num; + if(car5Num != -1) info->carThreshold[5] = info->carThreshold[4] + car5Num; + else info->carThreshold[5] = info->carThreshold[4] + oldCar5Num; + if(copNum != -1) info->copThreshold = info->carThreshold[5] + copNum; + else info->copThreshold = info->carThreshold[5] + oldCopNum; + if(gang0Num != -1) info->gangThreshold[0] = info->copThreshold + gang0Num; + else info->gangThreshold[0] = info->copThreshold + oldGang0Num; + if(gang1Num != -1) info->gangThreshold[1] = info->gangThreshold[0] + gang1Num; + else info->gangThreshold[1] = info->gangThreshold[0] + oldGang1Num; + if(gang2Num != -1) info->gangThreshold[2] = info->gangThreshold[1] + gang2Num; + else info->gangThreshold[2] = info->gangThreshold[1] + oldGang2Num; + if(gang3Num != -1) info->gangThreshold[3] = info->gangThreshold[2] + gang3Num; + else info->gangThreshold[3] = info->gangThreshold[2] + oldGang3Num; + if(gang4Num != -1) info->gangThreshold[4] = info->gangThreshold[3] + gang4Num; + else info->gangThreshold[4] = info->gangThreshold[3] + oldGang4Num; + if(gang5Num != -1) info->gangThreshold[5] = info->gangThreshold[4] + gang5Num; + else info->gangThreshold[5] = info->gangThreshold[4] + oldGang5Num; + if(gang6Num != -1) info->gangThreshold[6] = info->gangThreshold[5] + gang6Num; + else info->gangThreshold[6] = info->gangThreshold[5] + oldGang6Num; + if(gang7Num != -1) info->gangThreshold[7] = info->gangThreshold[6] + gang7Num; + else info->gangThreshold[7] = info->gangThreshold[6] + oldGang7Num; + if(gang8Num != -1) info->gangThreshold[8] = info->gangThreshold[7] + gang8Num; + else info->gangThreshold[8] = info->gangThreshold[7] + oldGang8Num; + + CheckZoneInfo(info); +} + +void +CTheZones::SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + if(pedDensity != -1) info->pedDensity = pedDensity; + if(copDensity != -1) info->copDensity = copDensity; + if(gang0Density != -1) info->gangDensity[0] = gang0Density; + if(gang1Density != -1) info->gangDensity[1] = gang1Density; + if(gang2Density != -1) info->gangDensity[2] = gang2Density; + if(gang3Density != -1) info->gangDensity[3] = gang3Density; + if(gang4Density != -1) info->gangDensity[4] = gang4Density; + if(gang5Density != -1) info->gangDensity[5] = gang5Density; + if(gang6Density != -1) info->gangDensity[6] = gang6Density; + if(gang7Density != -1) info->gangDensity[7] = gang7Density; + if(gang8Density != -1) info->gangDensity[8] = gang8Density; +} + +void +CTheZones::SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity; +} + +void +CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity; +} + +void +CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup) +{ + CZone *zone; + zone = GetZone(zoneid); + if(zone->type == ZONE_AUDIO || zone->type == ZONE_TYPE1 || zone->type == ZONE_TYPE2) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedGroup = pedgroup; +} + +int16 +CTheZones::FindAudioZone(CVector *pos) +{ + int i; + + for(i = 0; i < NumberOfAudioZones; i++) + if(PointLiesWithinZone(*pos, GetZone(AudioZoneArray[i]))) + return i; + return -1; +} + +eLevelName +CTheZones::FindZoneForPoint(const CVector &pos) +{ + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("IND_ZON")))) + return LEVEL_INDUSTRIAL; + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("COM_ZON")))) + return LEVEL_COMMERCIAL; + if(PointLiesWithinZone(pos, GetZone(FindZoneByLabelAndReturnIndex("SUB_ZON")))) + return LEVEL_SUBURBAN; + return LEVEL_NONE; +} + +void +CTheZones::AddZoneToAudioZoneArray(CZone *zone) +{ + int i, z; + + if(zone->type != ZONE_AUDIO) + return; + + /* This is a bit stupid */ + z = -1; + for(i = 0; i < NUMZONES; i++) + if(&ZoneArray[i] == zone) + z = i; + AudioZoneArray[NumberOfAudioZones++] = z; +} + +void +CTheZones::InitialiseAudioZoneArray(void) +{ + bool gonext; + CZone *zone; + + gonext = false; + zone = &ZoneArray[0]; + // Go deep first, + // set gonext when backing up a level to visit the next child + while(zone) + if(gonext){ + AddZoneToAudioZoneArray(zone); + if(zone->next){ + gonext = false; + zone = zone->next; + }else + zone = zone->parent; + }else if(zone->child) + zone = zone->child; + else{ + AddZoneToAudioZoneArray(zone); + if(zone->next) + zone = zone->next; + else{ + gonext = true; + zone = zone->parent; + } + } +} + +void +CTheZones::SaveAllZones(uint8 *buffer, uint32 *length) +{ + int i; + + *length = 8 + 12 + + NUMZONES*56 + 2*NUMZONES*58 + 4 + + NUMMAPZONES*56 + NUMAUDIOZONES*2 + 4; + + buffer[0] = 'Z'; + buffer[1] = 'N'; + buffer[2] = 'S'; + buffer[3] = '\0'; + *(uint32*)(buffer+4) = *length - 8; + buffer += 8; + + *(int32*)(buffer) = GetIndexForZonePointer(m_pPlayersZone); + *(int32*)(buffer+4) = m_CurrLevel; + *(int16*)(buffer+8) = FindIndex; + *(int16*)(buffer+10) = 0; + buffer += 12; + + for(i = 0; i < NUMZONES; i++){ + memcpy(buffer, ZoneArray[i].name, 8); + *(float*)(buffer+8) = ZoneArray[i].minx; + *(float*)(buffer+12) = ZoneArray[i].miny; + *(float*)(buffer+16) = ZoneArray[i].minz; + *(float*)(buffer+20) = ZoneArray[i].maxx; + *(float*)(buffer+24) = ZoneArray[i].maxy; + *(float*)(buffer+28) = ZoneArray[i].maxz; + *(int32*)(buffer+32) = ZoneArray[i].type; + *(int32*)(buffer+36) = ZoneArray[i].level; + *(int16*)(buffer+40) = ZoneArray[i].zoneinfoDay; + *(int16*)(buffer+42) = ZoneArray[i].zoneinfoNight; + *(int32*)(buffer+44) = GetIndexForZonePointer(ZoneArray[i].child); + *(int32*)(buffer+48) = GetIndexForZonePointer(ZoneArray[i].parent); + *(int32*)(buffer+52) = GetIndexForZonePointer(ZoneArray[i].next); + buffer += 56; + } + + for(i = 0; i < 2*NUMZONES; i++){ + *(int16*)(buffer) = ZoneInfoArray[i].carDensity; + *(int16*)(buffer+2) = ZoneInfoArray[i].carThreshold[0]; + *(int16*)(buffer+4) = ZoneInfoArray[i].carThreshold[1]; + *(int16*)(buffer+6) = ZoneInfoArray[i].carThreshold[2]; + *(int16*)(buffer+8) = ZoneInfoArray[i].carThreshold[3]; + *(int16*)(buffer+10) = ZoneInfoArray[i].carThreshold[4]; + *(int16*)(buffer+12) = ZoneInfoArray[i].carThreshold[5]; + *(int16*)(buffer+14) = ZoneInfoArray[i].copThreshold; + *(int16*)(buffer+16) = ZoneInfoArray[i].gangThreshold[0]; + *(int16*)(buffer+18) = ZoneInfoArray[i].gangThreshold[1]; + *(int16*)(buffer+20) = ZoneInfoArray[i].gangThreshold[2]; + *(int16*)(buffer+22) = ZoneInfoArray[i].gangThreshold[3]; + *(int16*)(buffer+24) = ZoneInfoArray[i].gangThreshold[4]; + *(int16*)(buffer+26) = ZoneInfoArray[i].gangThreshold[5]; + *(int16*)(buffer+28) = ZoneInfoArray[i].gangThreshold[6]; + *(int16*)(buffer+30) = ZoneInfoArray[i].gangThreshold[7]; + *(int16*)(buffer+32) = ZoneInfoArray[i].gangThreshold[8]; + *(uint16*)(buffer+34) = ZoneInfoArray[i].pedDensity; + *(uint16*)(buffer+36) = ZoneInfoArray[i].copDensity; + *(uint16*)(buffer+38) = ZoneInfoArray[i].gangDensity[0]; + *(uint16*)(buffer+40) = ZoneInfoArray[i].gangDensity[1]; + *(uint16*)(buffer+42) = ZoneInfoArray[i].gangDensity[2]; + *(uint16*)(buffer+44) = ZoneInfoArray[i].gangDensity[3]; + *(uint16*)(buffer+46) = ZoneInfoArray[i].gangDensity[4]; + *(uint16*)(buffer+48) = ZoneInfoArray[i].gangDensity[5]; + *(uint16*)(buffer+50) = ZoneInfoArray[i].gangDensity[6]; + *(uint16*)(buffer+52) = ZoneInfoArray[i].gangDensity[7]; + *(uint16*)(buffer+54) = ZoneInfoArray[i].gangDensity[8]; + *(uint16*)(buffer+56) = ZoneInfoArray[i].pedGroup; + buffer += 58; + } + + *(uint16*)(buffer) = TotalNumberOfZones; + *(uint16*)(buffer+2) = TotalNumberOfZoneInfos; + buffer += 4; + + for(i = 0; i < NUMMAPZONES; i++){ + memcpy(buffer, MapZoneArray[i].name, 8); + *(float*)(buffer+8) = MapZoneArray[i].minx; + *(float*)(buffer+12) = MapZoneArray[i].miny; + *(float*)(buffer+16) = MapZoneArray[i].minz; + *(float*)(buffer+20) = MapZoneArray[i].maxx; + *(float*)(buffer+24) = MapZoneArray[i].maxy; + *(float*)(buffer+28) = MapZoneArray[i].maxz; + *(int32*)(buffer+32) = MapZoneArray[i].type; + *(int32*)(buffer+36) = MapZoneArray[i].level; + *(int16*)(buffer+40) = MapZoneArray[i].zoneinfoDay; + *(int16*)(buffer+42) = MapZoneArray[i].zoneinfoNight; +#ifdef STANDALONE + // BUG: GetIndexForZonePointer uses ZoneArray + // so indices will be unpredictable with different memory layout + assert(0); +#endif + *(int32*)(buffer+44) = GetIndexForZonePointer(MapZoneArray[i].child); + *(int32*)(buffer+48) = GetIndexForZonePointer(MapZoneArray[i].parent); + *(int32*)(buffer+52) = GetIndexForZonePointer(MapZoneArray[i].next); + buffer += 56; + } + + for(i = 0; i < NUMAUDIOZONES; i++){ + *(int16*)buffer = AudioZoneArray[i]; + buffer += 2; + } + + *(uint16*)(buffer) = TotalNumberOfMapZones; + *(uint16*)(buffer+2) = NumberOfAudioZones; +} + +void +CTheZones::LoadAllZones(uint8 *buffer, uint32 length) +{ + int i; + + assert(length == 8 + 12 + + NUMZONES*56 + 2*NUMZONES*58 + 4 + + NUMMAPZONES*56 + NUMAUDIOZONES*2 + 4); + assert(buffer[0] == 'Z'); + assert(buffer[1] == 'N'); + assert(buffer[2] == 'S'); + assert(buffer[3] == '\0'); + assert(*(uint32*)(buffer+4) == length - 8); + buffer += 8; + + m_pPlayersZone = GetPointerForZoneIndex(*(int32*)(buffer)); + m_CurrLevel = (eLevelName)*(int32*)(buffer+4); + FindIndex = *(int16*)(buffer+8); + assert(*(int16*)(buffer+10) == 0); + buffer += 12; + + for(i = 0; i < NUMZONES; i++){ + memcpy(ZoneArray[i].name, buffer, 8); + ZoneArray[i].minx = *(float*)(buffer+8); + ZoneArray[i].miny = *(float*)(buffer+12); + ZoneArray[i].minz = *(float*)(buffer+16); + ZoneArray[i].maxx = *(float*)(buffer+20); + ZoneArray[i].maxy = *(float*)(buffer+24); + ZoneArray[i].maxz = *(float*)(buffer+28); + ZoneArray[i].type = (eZoneType)*(int32*)(buffer+32); + ZoneArray[i].level = (eLevelName)*(int32*)(buffer+36); + ZoneArray[i].zoneinfoDay = *(int16*)(buffer+40); + ZoneArray[i].zoneinfoNight = *(int16*)(buffer+42); + ZoneArray[i].child = GetPointerForZoneIndex(*(int32*)(buffer+44)); + ZoneArray[i].parent = GetPointerForZoneIndex(*(int32*)(buffer+48)); + ZoneArray[i].next = GetPointerForZoneIndex(*(int32*)(buffer+52)); + buffer += 56; + } + + for(i = 0; i < 2*NUMZONES; i++){ + ZoneInfoArray[i].carDensity = *(int16*)(buffer); + ZoneInfoArray[i].carThreshold[0] = *(int16*)(buffer+2); + ZoneInfoArray[i].carThreshold[1] = *(int16*)(buffer+4); + ZoneInfoArray[i].carThreshold[2] = *(int16*)(buffer+6); + ZoneInfoArray[i].carThreshold[3] = *(int16*)(buffer+8); + ZoneInfoArray[i].carThreshold[4] = *(int16*)(buffer+10); + ZoneInfoArray[i].carThreshold[5] = *(int16*)(buffer+12); + ZoneInfoArray[i].copThreshold = *(int16*)(buffer+14); + ZoneInfoArray[i].gangThreshold[0] = *(int16*)(buffer+16); + ZoneInfoArray[i].gangThreshold[1] = *(int16*)(buffer+18); + ZoneInfoArray[i].gangThreshold[2] = *(int16*)(buffer+20); + ZoneInfoArray[i].gangThreshold[3] = *(int16*)(buffer+22); + ZoneInfoArray[i].gangThreshold[4] = *(int16*)(buffer+24); + ZoneInfoArray[i].gangThreshold[5] = *(int16*)(buffer+26); + ZoneInfoArray[i].gangThreshold[6] = *(int16*)(buffer+28); + ZoneInfoArray[i].gangThreshold[7] = *(int16*)(buffer+30); + ZoneInfoArray[i].gangThreshold[8] = *(int16*)(buffer+32); + ZoneInfoArray[i].pedDensity = *(uint16*)(buffer+34); + ZoneInfoArray[i].copDensity = *(uint16*)(buffer+36); + ZoneInfoArray[i].gangDensity[0] = *(uint16*)(buffer+38); + ZoneInfoArray[i].gangDensity[1] = *(uint16*)(buffer+40); + ZoneInfoArray[i].gangDensity[2] = *(uint16*)(buffer+42); + ZoneInfoArray[i].gangDensity[3] = *(uint16*)(buffer+44); + ZoneInfoArray[i].gangDensity[4] = *(uint16*)(buffer+46); + ZoneInfoArray[i].gangDensity[5] = *(uint16*)(buffer+48); + ZoneInfoArray[i].gangDensity[6] = *(uint16*)(buffer+50); + ZoneInfoArray[i].gangDensity[7] = *(uint16*)(buffer+52); + ZoneInfoArray[i].gangDensity[8] = *(uint16*)(buffer+54); + ZoneInfoArray[i].pedGroup = *(uint16*)(buffer+56); + buffer += 58; + } + + TotalNumberOfZones = *(uint16*)(buffer); + TotalNumberOfZoneInfos = *(uint16*)(buffer+2); + buffer += 4; + + for(i = 0; i < NUMMAPZONES; i++){ + memcpy(MapZoneArray[i].name, buffer, 8); + MapZoneArray[i].minx = *(float*)(buffer+8); + MapZoneArray[i].miny = *(float*)(buffer+12); + MapZoneArray[i].minz = *(float*)(buffer+16); + MapZoneArray[i].maxx = *(float*)(buffer+20); + MapZoneArray[i].maxy = *(float*)(buffer+24); + MapZoneArray[i].maxz = *(float*)(buffer+28); + MapZoneArray[i].type = (eZoneType)*(int32*)(buffer+32); + MapZoneArray[i].level = (eLevelName)*(int32*)(buffer+36); + MapZoneArray[i].zoneinfoDay = *(int16*)(buffer+40); + MapZoneArray[i].zoneinfoNight = *(int16*)(buffer+42); +#ifdef STANDALONE + // BUG: GetPointerForZoneIndex uses ZoneArray + // so pointers will be unpredictable with different memory layout + assert(0); +#endif + MapZoneArray[i].child = GetPointerForZoneIndex(*(int32*)(buffer+44)); + MapZoneArray[i].parent = GetPointerForZoneIndex(*(int32*)(buffer+48)); + MapZoneArray[i].next = GetPointerForZoneIndex(*(int32*)(buffer+52)); + buffer += 56; + } + + for(i = 0; i < NUMAUDIOZONES; i++){ + AudioZoneArray[i] = *(int16*)buffer; + buffer += 2; + } + + TotalNumberOfMapZones = *(uint16*)(buffer); + NumberOfAudioZones = *(uint16*)(buffer+2); +} + + +STARTPATCHES + InjectHook(0x4B5DD0, &CZone::GetTranslatedName, PATCH_JUMP); + InjectHook(0x4B5DE0, CTheZones::Init, PATCH_JUMP); + InjectHook(0x4B61D0, CTheZones::Update, PATCH_JUMP); + InjectHook(0x4B6210, CTheZones::CreateZone, PATCH_JUMP); + InjectHook(0x4B6380, CTheZones::CreateMapZone, PATCH_JUMP); + InjectHook(0x4B64C0, CTheZones::PostZoneCreation, PATCH_JUMP); + InjectHook(0x4B6500, CTheZones::InsertZoneIntoZoneHierarchy, PATCH_JUMP); + InjectHook(0x4B6530, CTheZones::InsertZoneIntoZoneHierRecursive, PATCH_JUMP); + InjectHook(0x4B65F0, CTheZones::ZoneIsEntirelyContainedWithinOtherZone, PATCH_JUMP); + InjectHook(0x4B6710, CTheZones::PointLiesWithinZone, PATCH_JUMP); + InjectHook(0x4B6910, CTheZones::GetLevelFromPosition, PATCH_JUMP); + InjectHook(0x4B69B0, CTheZones::FindSmallestZonePosition, PATCH_JUMP); + InjectHook(0x4B6790, CTheZones::FindSmallestZonePositionType, PATCH_JUMP); + InjectHook(0x4B6890, CTheZones::FindSmallestZonePositionILN, PATCH_JUMP); + InjectHook(0x4B6800, CTheZones::FindZoneByLabelAndReturnIndex, PATCH_JUMP); + InjectHook(0x4B6FA0, CTheZones::GetZone, PATCH_JUMP); + InjectHook(0x4B84F0, CTheZones::GetPointerForZoneIndex, PATCH_JUMP); + InjectHook(0x4B6A10, CTheZones::GetZoneInfo, PATCH_JUMP); + InjectHook(0x4B6FB0, CTheZones::GetZoneInfoForTimeOfDay, PATCH_JUMP); + InjectHook(0x4B6A50, CTheZones::SetZoneCarInfo, PATCH_JUMP); + InjectHook(0x4B6DC0, CTheZones::SetZonePedInfo, PATCH_JUMP); + InjectHook(0x4B6EB0, CTheZones::SetCarDensity, PATCH_JUMP); + InjectHook(0x4B6F00, CTheZones::SetPedDensity, PATCH_JUMP); + InjectHook(0x4B6F50, CTheZones::SetPedGroup, PATCH_JUMP); + InjectHook(0x4B83E0, CTheZones::FindAudioZone, PATCH_JUMP); + InjectHook(0x4B8430, CTheZones::FindZoneForPoint, PATCH_JUMP); + InjectHook(0x4B8340, CTheZones::AddZoneToAudioZoneArray, PATCH_JUMP); + InjectHook(0x4B8510, CTheZones::SaveAllZones, PATCH_JUMP); + InjectHook(0x4B8950, CTheZones::LoadAllZones, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/Zones.h b/src/core/Zones.h new file mode 100644 index 00000000..bf3957de --- /dev/null +++ b/src/core/Zones.h @@ -0,0 +1,112 @@ +#pragma once + +#include "Game.h" + +enum eZoneType +{ + ZONE_AUDIO, + ZONE_TYPE1, // this should be NAVIG + ZONE_TYPE2, // this should be INFO...but all except MAPINFO get zoneinfo?? + ZONE_MAPZONE, +}; + +class CZone +{ +public: + char name[8]; + float minx; + float miny; + float minz; + float maxx; + float maxy; + float maxz; + eZoneType type; + eLevelName level; + int16 zoneinfoDay; + int16 zoneinfoNight; + CZone *child; + CZone *parent; + CZone *next; + + wchar *GetTranslatedName(void); +}; + +class CZoneInfo +{ +public: + // Car data + int16 carDensity; + int16 carThreshold[6]; + int16 copThreshold; + int16 gangThreshold[9]; + + // Ped data + uint16 pedDensity; + uint16 copDensity; + uint16 gangDensity[9]; + uint16 pedGroup; +}; + + +class CTheZones +{ +public: + static eLevelName &m_CurrLevel; + static CZone *&m_pPlayersZone; + static int16 &FindIndex; + + static uint16 &NumberOfAudioZones; + static int16 *AudioZoneArray; //[NUMAUDIOZONES]; + static uint16 &TotalNumberOfMapZones; + static uint16 &TotalNumberOfZones; + static CZone *ZoneArray; //[NUMZONES]; + static CZone *MapZoneArray; //[NUMMAPZONES]; + static uint16 &TotalNumberOfZoneInfos; + static CZoneInfo *ZoneInfoArray; //[2*NUMZONES]; + + static void Init(void); + static void Update(void); + static void CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static void CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static CZone *GetZone(uint16 i) { return &ZoneArray[i]; } + static void PostZoneCreation(void); + static void InsertZoneIntoZoneHierarchy(CZone *zone); + static bool InsertZoneIntoZoneHierRecursive(CZone *z1, CZone *z2); + static bool ZoneIsEntirelyContainedWithinOtherZone(CZone *z1, CZone *z2); + static bool PointLiesWithinZone(const CVector &v, CZone *zone); + static eLevelName GetLevelFromPosition(CVector const &v); + static CZone *FindSmallestZonePosition(const CVector *v); + static CZone *FindSmallestZonePositionType(const CVector *v, eZoneType type); + static CZone *FindSmallestZonePositionILN(const CVector *v); + static int16 FindZoneByLabelAndReturnIndex(char *name); + static CZoneInfo *GetZoneInfo(const CVector *v, uint8 day); + static void GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info); + static void SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num); + static void SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity); + static void SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity); + static void SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity); + static void SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup); + static int16 FindAudioZone(CVector *pos); + static eLevelName FindZoneForPoint(const CVector &pos); + static CZone *GetPointerForZoneIndex(int32 i) { return i == -1 ? nil : &ZoneArray[i]; } + static int32 GetIndexForZonePointer(CZone *zone) { return zone == nil ? -1 : zone - ZoneArray; } + static void AddZoneToAudioZoneArray(CZone *zone); + static void InitialiseAudioZoneArray(void); + static void SaveAllZones(uint8 *buffer, uint32 *length); + static void LoadAllZones(uint8 *buffer, uint32 length); +}; diff --git a/src/core/common.h b/src/core/common.h new file mode 100644 index 00000000..79626acb --- /dev/null +++ b/src/core/common.h @@ -0,0 +1,178 @@ +#pragma once + +#define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES +#pragma warning(disable: 4244) // int to float +#pragma warning(disable: 4800) // int to bool +#pragma warning(disable: 4305) // double to float +#pragma warning(disable: 4838) // narrowing conversion +#pragma warning(disable: 4996) // POSIX names + +#include <stdint.h> +#include <math.h> +//#include <assert.h> +#include <new> + +#ifdef WITHD3D +#include <windows.h> +#include <d3d8types.h> +#endif + +#include <rwcore.h> +#include <rpworld.h> + +#define rwVENDORID_ROCKSTAR 0x0253F2 + +// Get rid of bullshit windows definitions, we're not running on an 8086 +#ifdef far +#undef far +#endif +#ifdef near +#undef near +#endif + +typedef uint8_t uint8; +typedef int8_t int8; +typedef uint16_t uint16; +typedef int16_t int16; +typedef uint32_t uint32; +typedef int32_t int32; +typedef uintptr_t uintptr; +typedef uint64_t uint64; +typedef int64_t int64; +// hardcode ucs-2 +typedef uint16_t wchar; + +#define nil nullptr + +#include "config.h" + +#define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1)) + +// PDP-10 like byte functions +#define MASK(p, s) (((1<<(s))-1) << (p)) +inline uint32 dpb(uint32 b, uint32 p, uint32 s, uint32 w) +{ + uint32 m = MASK(p,s); + return w & ~m | b<<p & m; +} +inline uint32 ldb(uint32 p, uint32 s, uint32 w) +{ + return w>>p & (1<<s)-1; +} + + +// little hack +extern void **rwengine; +#define RwEngineInstance (*rwengine) + +#include "skeleton.h" +#include "Draw.h" + +#define DEFAULT_SCREEN_WIDTH (640) +#define DEFAULT_SCREEN_HEIGHT (448) +#define DEFAULT_ASPECT_RATIO (4.0f/3.0f) + +// game uses maximumWidth/Height, but this probably won't work +// with RW windowed mode +#define SCREEN_WIDTH ((float)RsGlobal.width) +#define SCREEN_HEIGHT ((float)RsGlobal.height) +#define SCREEN_ASPECT_RATIO (CDraw::GetAspectRatio()) + +// This scales from PS2 pixel coordinates to the real resolution +#define SCREEN_STRETCH_X(a) ((a) * (float) SCREEN_WIDTH / DEFAULT_SCREEN_WIDTH) +#define SCREEN_STRETCH_Y(a) ((a) * (float) SCREEN_HEIGHT / DEFAULT_SCREEN_HEIGHT) +#define SCREEN_STRETCH_FROM_RIGHT(a) (SCREEN_WIDTH - SCREEN_STRETCH_X(a)) +#define SCREEN_STRETCH_FROM_BOTTOM(a) (SCREEN_HEIGHT - SCREEN_STRETCH_Y(a)) + +// This scales from PS2 pixel coordinates while optionally maintaining the aspect ratio +#define SCREEN_SCALE_X(a) SCREEN_SCALE_AR(SCREEN_STRETCH_X(a)) +#define SCREEN_SCALE_Y(a) SCREEN_STRETCH_Y(a) +#define SCREEN_SCALE_FROM_RIGHT(a) (SCREEN_WIDTH - SCREEN_SCALE_X(a)) +#define SCREEN_SCALE_FROM_BOTTOM(a) (SCREEN_HEIGHT - SCREEN_SCALE_Y(a)) + +#ifdef ASPECT_RATIO_SCALE +#define SCREEN_SCALE_AR(a) ((a) * (4.0f / 3.0f) / SCREEN_ASPECT_RATIO) +#else +#define SCREEN_SCALE_AR(a) (a) +#endif + +#include "math/Vector.h" +#include "math/Vector2D.h" +#include "math/Matrix.h" +#include "math/Rect.h" + +class CRGBA +{ +public: + union + { + uint32 color32; + struct { uint8 r, g, b, a; }; + struct { uint8 red, green, blue, alpha; }; +#ifdef RWCORE_H + struct { RwRGBA rwRGBA; }; +#endif + }; + + CRGBA(void) { } + CRGBA(uint8 r, uint8 g, uint8 b, uint8 a) : r(r), g(g), b(b), a(a) { } +#ifdef RWCORE_H + operator RwRGBA &(void) { + return rwRGBA; + } + + operator RwRGBA *(void) { + return &rwRGBA; + } + + operator RwRGBA (void) const { + return rwRGBA; + } +#endif +}; + +#define clamp(v, low, high) ((v)<(low) ? (low) : (v)>(high) ? (high) : (v)) + +inline float sq(float x) { return x*x; } +#define SQR(x) ((x) * (x)) + +#define PI M_PI +#define DEGTORAD(x) ((x) * PI / 180.0f) +#define RADTODEG(x) ((x) * 180.0f / PI) + +#ifdef USE_PS2_RAND +#define MYRAND_MAX 65535 +#else +#define MYRAND_MAX 32767 +#endif + +int myrand(void); +void mysrand(unsigned int seed); + +void re3_debug(char *format, ...); +void re3_trace(const char *filename, unsigned int lineno, const char *func, char *format, ...); +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func); + +#define DEBUGBREAK() __debugbreak(); + +#define debug(f, ...) re3_debug("[DBG]: " f, __VA_ARGS__) +#define DEV(f, ...) re3_debug("[DEV]: " f, __VA_ARGS__) +#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f, __VA_ARGS__) +#define Error(f, ...) re3_debug("[ERROR]: " f, __VA_ARGS__) + +#define assert(_Expression) (void)( (!!(_Expression)) || (re3_assert(#_Expression, __FILE__, __LINE__, __FUNCTION__), 0) ) +#define ASSERT assert + +#define _TODO(x) +#define _TODOCONST(x) (x) + +#define VALIDATE_SIZE(struc, size) static_assert(sizeof(struc) == size, "Invalid structure size of " #struc) +#define VALIDATE_OFFSET(struc, member, offset) static_assert(offsetof(struc, member) == offset, "The offset of " #member " in " #struc " is not " #offset "...") + +#define PERCENT(x, p) ((float(x) * (float(p) / 100.0f))) +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#define BIT(num) (1<<(num)) + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) diff --git a/src/core/config.h b/src/core/config.h new file mode 100644 index 00000000..8cb02190 --- /dev/null +++ b/src/core/config.h @@ -0,0 +1,116 @@ +#pragma once + +enum Config { + NUMCDIMAGES = 12, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 8, // additional cdimages + + MODELINFOSIZE = 5500, + TXDSTORESIZE = 850, + EXTRADIRSIZE = 128, + + SIMPLEMODELSIZE = 5000, + TIMEMODELSIZE = 30, + CLUMPMODELSIZE = 5, + PEDMODELSIZE = 90, + VEHICLEMODELSIZE = 120, + TWODFXSIZE = 2000, + + MAXVEHICLESLOADED = 50, // 70 on mobile + + NUMOBJECTINFO = 168, // object.dat + + // Pool sizes + NUMPTRNODES = 30000, // 26000 on PS2 + NUMENTRYINFOS = 5400, // 3200 on PS2 + NUMPEDS = 140, // 90 on PS2 + NUMVEHICLES = 110, // 70 on PS2 + NUMBUILDINGS = 5500, // 4915 on PS2 + NUMTREADABLES = 1214, + NUMOBJECTS = 450, + NUMDUMMIES = 2802, // 2368 on PS2 + NUMAUDIOSCRIPTOBJECTS = 256, + + // Link list lengths + // TODO: alpha list + NUMCOLCACHELINKS = 200, + NUMREFERENCES = 800, + + // Zones + NUMAUDIOZONES = 36, + NUMZONES = 50, + NUMMAPZONES = 25, + + // Cull zones + NUMCULLZONES = 512, + NUMATTRIBZONES = 288, + NUMZONEINDICES = 55000, + + NUMHANDLINGS = 57, + + PATHNODESIZE = 4500, + + NUMWEATHERS = 4, + NUMHOURS = 24, + + NUMEXTRADIRECTIONALS = 4, + NUMANTENNAS = 8, + NUMCORONAS = 56, + NUMPOINTLIGHTS = 32, + + NUMONSCREENTIMERENTRIES = 1, + NUMRADARBLIPS = 32, + NUMPICKUPS = 336, +}; + +// We'll use this once we're ready to become independent of the game +// Use it to mark bugs in the code that will prevent the game from working then +//#define STANDALONE + +// We don't expect to compile for PS2 or Xbox +// but it might be interesting for documentation purposes +#define GTA_PC +//#define GTA_PS2 +//#define GTA_XBOX + +// This enables things from the PS2 version on PC +#define GTA_PS2_STUFF + +// This is enabled for all released games. +// any debug stuff that isn't left in any game is not in FINAL +//#define FINAL + +// This is enabled for all released games except mobile +// any debug stuff that is only left in mobile, is not in MASTER +//#define MASTER + +#if defined GTA_PS2 +# define RANDOMSPLASH +#elif defined GTA_PC +# define GTA3_1_1_PATCH +# ifdef GTA_PS2_STUFF +//# define USE_PS2_RAND // this is unsafe until we have the game reversed +# define RANDOMSPLASH // use random splash as on PS2 +# define PS2_MATFX +# endif +#elif defined GTA_XBOX +#endif + +#ifdef MASTER + // only in master builds +#else + // not in master builds +#endif + +#ifdef FINAL + // in all games +# define USE_MY_DOCUMENTS // use my documents directory for user files +#else + // not in any game +# define NASTY_GAME // nasty game for all languages +# define NO_MOVIES // disable intro videos +# define CHATTYSPLASH // print what the game is loading +#endif + +#define FIX_BUGS // fix bugs in the game, TODO: use this more +#define KANGAROO_CHEAT +#define ASPECT_RATIO_SCALE diff --git a/src/core/debugmenu_public.h b/src/core/debugmenu_public.h new file mode 100644 index 00000000..778e7afe --- /dev/null +++ b/src/core/debugmenu_public.h @@ -0,0 +1,154 @@ + +extern "C" { + +typedef void (*TriggerFunc)(void); + +struct DebugMenuEntry; + +typedef DebugMenuEntry *(*DebugMenuAddInt8_TYPE)(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt16_TYPE)(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt32_TYPE)(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddInt64_TYPE)(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt8_TYPE)(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt16_TYPE)(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt32_TYPE)(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddUInt64_TYPE)(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings); +typedef DebugMenuEntry *(*DebugMenuAddFloat32_TYPE)(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound); +typedef DebugMenuEntry *(*DebugMenuAddFloat64_TYPE)(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound); +typedef DebugMenuEntry *(*DebugMenuAddCmd_TYPE)(const char *path, const char *name, TriggerFunc triggerFunc); +typedef void (*DebugMenuEntrySetWrap_TYPE)(DebugMenuEntry *e, bool wrap); +typedef void (*DebugMenuEntrySetStrings_TYPE)(DebugMenuEntry *e, const char **strings); +typedef void (*DebugMenuEntrySetAddress_TYPE)(DebugMenuEntry *e, void *addr); + +struct DebugMenuAPI +{ + bool isLoaded; + HMODULE module; + DebugMenuAddInt8_TYPE addint8; + DebugMenuAddInt16_TYPE addint16; + DebugMenuAddInt32_TYPE addint32; + DebugMenuAddInt64_TYPE addint64; + DebugMenuAddUInt8_TYPE adduint8; + DebugMenuAddUInt16_TYPE adduint16; + DebugMenuAddUInt32_TYPE adduint32; + DebugMenuAddUInt64_TYPE adduint64; + DebugMenuAddFloat32_TYPE addfloat32; + DebugMenuAddFloat64_TYPE addfloat64; + DebugMenuAddCmd_TYPE addcmd; + DebugMenuEntrySetWrap_TYPE setwrap; + DebugMenuEntrySetStrings_TYPE setstrings; + DebugMenuEntrySetAddress_TYPE setaddress; +}; +extern DebugMenuAPI gDebugMenuAPI; + +inline DebugMenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) +{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) +{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc) +{ return gDebugMenuAPI.addcmd(path, name, triggerFunc); } +inline void DebugMenuEntrySetWrap(DebugMenuEntry *e, bool wrap) +{ gDebugMenuAPI.setwrap(e, wrap); } +inline void DebugMenuEntrySetStrings(DebugMenuEntry *e, const char **strings) +{ gDebugMenuAPI.setstrings(e, strings); } +inline void DebugMenuEntrySetAddress(DebugMenuEntry *e, void *addr) +{ gDebugMenuAPI.setaddress(e, addr); } + +inline bool DebugMenuLoad(void) +{ + if(gDebugMenuAPI.isLoaded) + return true; + HMODULE mod = LoadLibraryA("debugmenu"); + if(mod == nil){ + char modulePath[MAX_PATH]; + HMODULE dllModule; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&gDebugMenuAPI, &dllModule); + GetModuleFileNameA(dllModule, modulePath, MAX_PATH); + char *p = strchr(modulePath, '\\'); + if(p) p[1] = '\0'; + strcat(modulePath, "debugmenu"); + mod = LoadLibraryA(modulePath); + } + if(mod == nil) + return false; + gDebugMenuAPI.addint8 = (DebugMenuAddInt8_TYPE)GetProcAddress(mod, "DebugMenuAddInt8"); + gDebugMenuAPI.addint16 = (DebugMenuAddInt16_TYPE)GetProcAddress(mod, "DebugMenuAddInt16"); + gDebugMenuAPI.addint32 = (DebugMenuAddInt32_TYPE)GetProcAddress(mod, "DebugMenuAddInt32"); + gDebugMenuAPI.addint64 = (DebugMenuAddInt64_TYPE)GetProcAddress(mod, "DebugMenuAddInt64"); + gDebugMenuAPI.adduint8 = (DebugMenuAddUInt8_TYPE)GetProcAddress(mod, "DebugMenuAddUInt8"); + gDebugMenuAPI.adduint16 = (DebugMenuAddUInt16_TYPE)GetProcAddress(mod, "DebugMenuAddUInt16"); + gDebugMenuAPI.adduint32 = (DebugMenuAddUInt32_TYPE)GetProcAddress(mod, "DebugMenuAddUInt32"); + gDebugMenuAPI.adduint64 = (DebugMenuAddUInt64_TYPE)GetProcAddress(mod, "DebugMenuAddUInt64"); + gDebugMenuAPI.addfloat32 = (DebugMenuAddFloat32_TYPE)GetProcAddress(mod, "DebugMenuAddFloat32"); + gDebugMenuAPI.addfloat64 = (DebugMenuAddFloat64_TYPE)GetProcAddress(mod, "DebugMenuAddFloat64"); + gDebugMenuAPI.addcmd = (DebugMenuAddCmd_TYPE)GetProcAddress(mod, "DebugMenuAddCmd"); + gDebugMenuAPI.setwrap = (DebugMenuEntrySetWrap_TYPE)GetProcAddress(mod, "DebugMenuEntrySetWrap"); + gDebugMenuAPI.setstrings = (DebugMenuEntrySetStrings_TYPE)GetProcAddress(mod, "DebugMenuEntrySetStrings"); + gDebugMenuAPI.setaddress = (DebugMenuEntrySetAddress_TYPE)GetProcAddress(mod, "DebugMenuEntrySetAddress"); + gDebugMenuAPI.isLoaded = true; + gDebugMenuAPI.module = mod; + return true; +} + +} + +// Also overload them for simplicity + +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) +{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) +{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) +{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) +{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } + +inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} diff --git a/src/core/main.cpp b/src/core/main.cpp new file mode 100644 index 00000000..e7f42780 --- /dev/null +++ b/src/core/main.cpp @@ -0,0 +1,891 @@ +#include "common.h" +#include "patcher.h" +#include "main.h" +#include "General.h" +#include "RwHelper.h" +#include "Clouds.h" +#include "Draw.h" +#include "Sprite2d.h" +#include "Renderer.h" +#include "Coronas.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "Glass.h" +#include "WaterCannon.h" +#include "SpecialFX.h" +#include "Shadows.h" +#include "Skidmarks.h" +#include "Antennas.h" +#include "Rubbish.h" +#include "Particle.h" +#include "Pickups.h" +#include "WeaponEffects.h" +#include "PointLights.h" +#include "Fluff.h" +#include "Replay.h" +#include "Camera.h" +#include "World.h" +#include "Ped.h" +#include "Font.h" +#include "Pad.h" +#include "Hud.h" +#include "User.h" +#include "Messages.h" +#include "Darkel.h" +#include "Garages.h" +#include "MusicManager.h" +#include "VisibilityPlugins.h" +#include "NodeName.h" +#include "DMAudio.h" +#include "CutsceneMgr.h" +#include "Lights.h" +#include "Credits.h" +#include "ZoneCull.h" +#include "Timecycle.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "Text.h" +#include "RpAnimBlend.h" +#include "Frontend.h" + +#define DEFAULT_VIEWWINDOW (tan(DEGTORAD(CDraw::GetFOV() * 0.5f))) + + +GlobalScene &Scene = *(GlobalScene*)0x726768; + +uint8 work_buff[55000]; +char gString[256]; +wchar *gUString = (wchar*)0x74B018; + +bool &b_FoundRecentSavedGameWantToLoad = *(bool*)0x95CDA8; + + +bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + +void RenderScene(void); +void RenderDebugShit(void); +void RenderEffects(void); +void Render2dStuff(void); +void RenderMenus(void); +void DoFade(void); +void Render2dStuffAfterFade(void); + +CSprite2d *LoadSplash(const char *name); +void DestroySplashScreen(void); + + +extern void (*DebugMenuProcess)(void); +extern void (*DebugMenuRender)(void); +void DebugMenuInit(void); +void DebugMenuPopulate(void); + +void PrintGameVersion(); + +RwRGBA gColourTop; + +void +InitialiseGame(void) +{ + LoadingScreen(nil, nil, "loadsc0"); + CGame::Initialise("DATA\\GTA3.DAT"); +} + +void +Idle(void *arg) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CPointLights::InitPerFrame(); + CGame::Process(); + DMAudio.Service(); + + if(CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()){ + FrontEndMenuManager.m_bStartGameLoading = true; + FrontEndMenuManager.m_bLoadingSavedGame = false; + return; + } + + if(FrontEndMenuManager.m_bStartGameLoading || b_FoundRecentSavedGameWantToLoad) + return; + + SetLightsWithTimeOfDayColour(Scene.world); + + if(arg == nil) + return; + + if((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.field_452 == 1) && + TheCamera.GetScreenFadeStatus() != FADE_2){ +#ifdef GTA_PC + // This is from SA, but it's nice for windowed mode + RwV2d pos; + pos.x = SCREEN_WIDTH/2.0f; + pos.y = SCREEN_HEIGHT/2.0f; + RsMouseSetPos(&pos); +#endif + CRenderer::ConstructRenderList(); + CRenderer::PreRender(); + + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + if(!DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255)) + return; + }else{ + if(!DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255)) + return; + } + + DefinedState(); + + // BUG. This has to be done BEFORE RwCameraBeginUpdate + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); + + RenderScene(); + RenderDebugShit(); + RenderEffects(); + + if((TheCamera.m_BlurType == MBLUR_NONE || TheCamera.m_BlurType == MBLUR_NORMAL) && + TheCamera.m_ScreenReductionPercentage > 0.0) + TheCamera.SetMotionBlurAlpha(150); + TheCamera.RenderMotionBlur(); + + Render2dStuff(); + }else{ + float viewWindow = DEFAULT_VIEWWINDOW; + CameraSize(Scene.camera, nil, viewWindow, DEFAULT_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ); + if(!RsCameraBeginUpdate(Scene.camera)) + return; + } + + RenderMenus(); +#ifndef FINAL + PrintGameVersion(); +#endif + DoFade(); + Render2dStuffAfterFade(); + CCredits::Render(); + DoRWStuffEndOfFrame(); + +// if(g_SlowMode) +// ProcessSlowMode(); +} + +void +FrontendIdle(void) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CPad::UpdatePads(); + FrontEndMenuManager.Process(); + + if(RsGlobal.quit) + return; + + float viewWindow = DEFAULT_VIEWWINDOW; + CameraSize(Scene.camera, nil, viewWindow, DEFAULT_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ); + if(!RsCameraBeginUpdate(Scene.camera)) + return; + + DefinedState(); + RenderMenus(); +#ifndef FINAL + PrintGameVersion(); +#endif + DoFade(); + Render2dStuffAfterFade(); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +bool +DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) +{ + CRGBA TopColor(TopRed, TopGreen, TopBlue, Alpha); + CRGBA BottomColor(BottomRed, BottomGreen, BottomBlue, Alpha); + + CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, SCREEN_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + + CSprite2d::InitPerFrame(); + + if(Alpha != 0) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), BottomColor, BottomColor, TopColor, TopColor); + + return true; +} + +bool +DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) +{ + CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, SCREEN_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, rwCAMERACLEARZ); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + + TheCamera.m_viewMatrix.Update(); + CClouds::RenderBackground(TopRed, TopGreen, TopBlue, BottomRed, BottomGreen, BottomBlue, Alpha); + + return true; +} + +void +DoRWStuffEndOfFrame(void) +{ + // CDebug::DebugDisplayTextBuffer(); + // FlushObrsPrintfs(); + RwCameraEndUpdate(Scene.camera); + RsCameraShowRaster(Scene.camera); +} + + +// This is certainly a very useful function +void +DoRWRenderHorizon(void) +{ + CClouds::RenderHorizon(); +} + +void +RenderScene(void) +{ + CClouds::Render(); + DoRWRenderHorizon(); + CRenderer::RenderRoads(); + CCoronas::RenderReflections(); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + CRenderer::RenderEverythingBarRoads(); + CRenderer::RenderBoats(); + DefinedState(); + CWaterLevel::RenderWater(); + CRenderer::RenderFadingInEntities(); + CRenderer::RenderVehiclesButNotBoats(); + CWeather::RenderRainStreaks(); +} + +void +RenderDebugShit(void) +{ + // CTheScripts::RenderTheScriptDebugLines() +} + +void +RenderEffects(void) +{ + CGlass::Render(); + CWaterCannons::Render(); + CSpecialFX::Render(); + CShadows::RenderStaticShadows(); + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CAntennas::Render(); + CRubbish::Render(); + CCoronas::Render(); + CParticle::Render(); + CPacManPickups::Render(); + CWeaponEffects::Render(); + CPointLights::RenderFogEffect(); + CMovingThings::Render(); + CRenderer::RenderFirstPersonVehicle(); +} + +void +Render2dStuff(void) +{ + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + + CReplay::Display(); + CPickups::RenderPickUpText(); + + if(TheCamera.m_WideScreenOn) + TheCamera.DrawBordersForWideScreen(); + + CPed *player = FindPlayerPed(); + int weaponType = 0; + if(player) + weaponType = player->GetWeapon()->m_eWeaponType; + + bool firstPersonWeapon = false; + int cammode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if(cammode == CCam::MODE_SNIPER || + cammode == CCam::MODE_SNIPER_RUN_AROUND || + cammode == CCam::MODE_ROCKET || + cammode == CCam::MODE_ROCKET_RUN_AROUND) + firstPersonWeapon = true; + + // Draw black border for sniper and rocket launcher + if((weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER) && firstPersonWeapon){ + CRGBA black(0, 0, 0, 255); + + // top and bottom strips + if (weaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(180)), black); + CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(170), SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + else { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(210)), black); + CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(210), SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH / 2 - SCREEN_SCALE_X(210), SCREEN_HEIGHT), black); + CSprite2d::DrawRect(CRect(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(210), 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + + MusicManager.DisplayRadioStationName(); +// TheConsole.Display(); +/* + if(CSceneEdit::m_bEditOn) + CSceneEdit::Draw(); + else +*/ + CHud::Draw(); + CUserDisplay::OnscnTimer.ProcessForDisplay(); + CMessages::Display(); + CDarkel::DrawMessages(); + CGarages::PrintMessages(); + CPad::PrintErrorMessage(); + CFont::DrawFonts(); + + DebugMenuRender(); +} + +void +RenderMenus(void) +{ + if(FrontEndMenuManager.m_bMenuActive) + FrontEndMenuManager.DrawFrontEnd(); +} + +bool &JustLoadedDontFadeInYet = *(bool*)0x95CDB4; +bool &StillToFadeOut = *(bool*)0x95CD99; +uint32 &TimeStartedCountingForFade = *(uint32*)0x9430EC; +uint32 &TimeToStayFadedBeforeFadeOut = *(uint32*)0x611564; + +void +DoFade(void) +{ + if(CTimer::GetIsPaused()) + return; + + if(JustLoadedDontFadeInYet){ + JustLoadedDontFadeInYet = false; + TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds(); + } + + if(StillToFadeOut){ + if(CTimer::GetTimeInMilliseconds() - TimeStartedCountingForFade > TimeToStayFadedBeforeFadeOut){ + StillToFadeOut = false; + TheCamera.Fade(3.0f, FADE_IN); + TheCamera.ProcessFade(); + TheCamera.ProcessMusicFade(); + }else{ + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + } + } + + if(CDraw::FadeValue != 0 || CMenuManager::m_PrefsBrightness < 256){ + CSprite2d *splash = LoadSplash(nil); + + CRGBA fadeColor; + CRect rect; + int fadeValue = CDraw::FadeValue; + float brightness = min(CMenuManager::m_PrefsBrightness, 256); + if(brightness <= 50) + brightness = 50; + if(FrontEndMenuManager.m_bMenuActive) + brightness = 256; + + if(TheCamera.m_FadeTargetIsSplashScreen) + fadeValue = 0; + + float fade = fadeValue + 256 - brightness; + if(fade == 0){ + fadeColor.r = 0; + fadeColor.g = 0; + fadeColor.b = 0; + fadeColor.a = 0; + }else{ + fadeColor.r = fadeValue * CDraw::FadeRed / fade; + fadeColor.g = fadeValue * CDraw::FadeGreen / fade; + fadeColor.b = fadeValue * CDraw::FadeBlue / fade; + int alpha = 255 - brightness*(256 - fadeValue)/256; + if(alpha < 0) + alpha = 0; + fadeColor.a = alpha; + } + + if(TheCamera.m_WideScreenOn){ + // what's this? + float y = SCREEN_HEIGHT/2 * TheCamera.m_ScreenReductionPercentage/100.0f; + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; + rect.top = y - 8.0f; + rect.bottom = SCREEN_HEIGHT - y - 8.0f; + }else{ + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; + rect.top = 0.0f; + rect.bottom = SCREEN_HEIGHT; + } + CSprite2d::DrawRect(rect, fadeColor); + + if(CDraw::FadeValue != 0 && TheCamera.m_FadeTargetIsSplashScreen){ + fadeColor.r = 255; + fadeColor.g = 255; + fadeColor.b = 255; + fadeColor.a = CDraw::FadeValue; + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), fadeColor, fadeColor, fadeColor, fadeColor); + } + } +} + +void +Render2dStuffAfterFade(void) +{ + CHud::DrawAfterFade(); + CFont::DrawFonts(); +} + +CSprite2d splash; +int splashTxdId = -1; + +CSprite2d* +LoadSplash(const char *name) +{ + RwTexDictionary *txd; + char filename[140]; + RwTexture *tex = nil; + + if(name == nil) + return &splash; + if(splashTxdId == -1) + splashTxdId = CTxdStore::AddTxdSlot("splash"); + + txd = CTxdStore::GetSlot(splashTxdId)->texDict; + if(txd) + tex = RwTexDictionaryFindNamedTexture(txd, name); + // if texture is found, splash was already set up below + + if(tex == nil){ + CFileMgr::SetDir("TXD\\"); + sprintf(filename, "%s.txd", name); + if(splash.m_pTexture) + splash.Delete(); + if(txd) + CTxdStore::RemoveTxd(splashTxdId); + CTxdStore::LoadTxd(splashTxdId, filename); + CTxdStore::AddRef(splashTxdId); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(splashTxdId); + splash.SetTexture(name); + CTxdStore::PopCurrentTxd(); + CFileMgr::SetDir(""); + } + + return &splash; +} + +void +DestroySplashScreen(void) +{ + splash.Delete(); + if(splashTxdId != -1) + CTxdStore::RemoveTxdSlot(splashTxdId); + splashTxdId = -1; +} + +float NumberOfChunksLoaded; +#define TOTALNUMCHUNKS 73.0f + +// TODO: compare with PS2 +void +LoadingScreen(const char *str1, const char *str2, const char *splashscreen) +{ + CSprite2d *splash; + +#ifndef RANDOMSPLASH + if(CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + splashscreen = "mainsc2"; + else + splashscreen = "mainsc1"; +#endif + + splash = LoadSplash(splashscreen); + + if(RsGlobal.quit) + return; + + if(DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)){ + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + if(str1){ + NumberOfChunksLoaded += 1; + + float hpos = SCREEN_SCALE_X(40); + float length = SCREEN_WIDTH - SCREEN_SCALE_X(100); + float vpos = SCREEN_HEIGHT - SCREEN_SCALE_Y(13); + float height = SCREEN_SCALE_Y(7); + CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(40, 53, 68, 255)); + + length *= NumberOfChunksLoaded/TOTALNUMCHUNKS; + CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(81, 106, 137, 255)); + + // this is done by the game but is unused + CFont::SetScale(SCREEN_SCALE_X(2), SCREEN_SCALE_Y(2)); + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + +#ifdef CHATTYSPLASH + // my attempt + static wchar tmpstr[80]; + float scale = SCREEN_SCALE_Y(0.8f); + vpos -= 50*scale; + CFont::SetScale(scale, scale); + CFont::SetPropOn(); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + AsciiToUnicode(str1, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); + vpos += 25*scale; + AsciiToUnicode(str2, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); +#endif + } + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } +} + +void +ResetLoadingScreenBar(void) +{ + NumberOfChunksLoaded = 0.0f; +} + +void +LoadingIslandScreen(const char *levelName) +{ + CSprite2d *splash; + wchar *name; + char str[100]; + wchar wstr[80]; + CRGBA col; + + splash = LoadSplash(nil); + name = TheText.Get(levelName); + if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + col = CRGBA(255, 255, 255, 255); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), col, col, col, col); + CFont::SetBackgroundOff(); + CFont::SetScale(1.5f, 1.5f); + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(150.0f); + CFont::SetFontStyle(FONT_HEADING); + sprintf(str, "WELCOME TO"); + AsciiToUnicode(str, wstr); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetColor(CRGBA(243, 237, 71, 255)); + CFont::SetScale(SCREEN_STRETCH_X(1.2f), SCREEN_STRETCH_Y(1.2f)); + CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_STRETCH_FROM_BOTTOM(110.0f), TheText.Get("WELCOME")); + TextCopy(wstr, name); + TheText.UpperCase(wstr); + CFont::SetColor(CRGBA(243, 237, 71, 255)); + CFont::SetScale(SCREEN_STRETCH_X(1.2f), SCREEN_STRETCH_Y(1.2f)); + CFont::PrintString(SCREEN_WIDTH-20, SCREEN_STRETCH_FROM_BOTTOM(80.0f), wstr); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +char* +GetLevelSplashScreen(int level) +{ + static char *splashScreens[4] = { + nil, + "splash1", + "splash2", + "splash3", + }; + + return splashScreens[level]; +} + +char* +GetRandomSplashScreen(void) +{ + int index; + static int index2 = 0; + static char splashName[128]; + static int splashIndex[24] = { + 25, 22, 4, 13, + 1, 21, 14, 16, + 10, 12, 5, 9, + 11, 18, 3, 2, + 19, 23, 7, 17, + 15, 6, 8, 20 + }; + + index = splashIndex[4*index2 + CGeneral::GetRandomNumberInRange(0, 3)]; + index2++; + if(index2 == 6) + index2 = 0; + sprintf(splashName, "loadsc%d", index); + return splashName; +} + +#include "rwcore.h" +#include "rpworld.h" +#include "rpmatfx.h" +#include "rpskin.h" +#include "rphanim.h" +#include "rtbmp.h" + +_TODO("temp, move this includes out of here") + +static RwBool +PluginAttach(void) +{ + if( !RpWorldPluginAttach() ) + { + printf("Couldn't attach world plugin\n"); + + return FALSE; + } + + if( !RpSkinPluginAttach() ) + { + printf("Couldn't attach RpSkin plugin\n"); + + return FALSE; + } + + if( !RpHAnimPluginAttach() ) + { + printf("Couldn't attach RpHAnim plugin\n"); + + return FALSE; + } + + if( !NodeNamePluginAttach() ) + { + printf("Couldn't attach node name plugin\n"); + + return FALSE; + } + + if( !CVisibilityPlugins::PluginAttach() ) + { + printf("Couldn't attach visibility plugins\n"); + + return FALSE; + } + + if( !RpAnimBlendPluginAttach() ) + { + printf("Couldn't attach RpAnimBlend plugin\n"); + + return FALSE; + } + + if( !RpMatFXPluginAttach() ) + { + printf("Couldn't attach RpMatFX plugin\n"); + + return FALSE; + } + + return TRUE; +} + +static RwBool +Initialise3D(void *param) +{ + if (RsRwInitialise(param)) + { + // + DebugMenuInit(); + DebugMenuPopulate(); + // + + return CGame::InitialiseRenderWare(); + } + + return (FALSE); +} + + +static void +Terminate3D(void) +{ + CGame::ShutdownRenderWare(); + + RsRwTerminate(); + + return; +} + +RsEventStatus +AppEventHandler(RsEvent event, void *param) +{ + switch( event ) + { + case rsINITIALISE: + { + CGame::InitialiseOnceBeforeRW(); + return RsInitialise() ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsCAMERASIZE: + { + + CameraSize(Scene.camera, (RwRect *)param, + DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + return rsEVENTPROCESSED; + } + + case rsRWINITIALISE: + { + return Initialise3D(param) ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsRWTERMINATE: + { + Terminate3D(); + + return rsEVENTPROCESSED; + } + + case rsTERMINATE: + { + CGame::FinalShutdown(); + + return rsEVENTPROCESSED; + } + + case rsPLUGINATTACH: + { + return PluginAttach() ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsINPUTDEVICEATTACH: + { + AttachInputDevices(); + + return rsEVENTPROCESSED; + } + + case rsIDLE: + { + Idle(param); + + return rsEVENTPROCESSED; + } + + case rsFRONTENDIDLE: + { + FrontendIdle(); + + return rsEVENTPROCESSED; + } + + case rsACTIVATE: + { + param ? DMAudio.ReacquireDigitalHandle() : DMAudio.ReleaseDigitalHandle(); + + return rsEVENTPROCESSED; + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +void PrintGameVersion() +{ + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(0.5f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetDropShadowPosition(0); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(CRGBA(235, 170, 50, 255)); + + strcpy(gString, "RE3"); + AsciiToUnicode(gString, gUString); + CFont::PrintString(SCREEN_SCALE_X(10.5f), SCREEN_SCALE_Y(8.0f), gUString); +} + +STARTPATCHES + InjectHook(0x48E480, Idle, PATCH_JUMP); + InjectHook(0x48E700, FrontendIdle, PATCH_JUMP); + + InjectHook(0x48CF10, DoRWStuffStartOfFrame, PATCH_JUMP); + InjectHook(0x48D040, DoRWStuffStartOfFrame_Horizon, PATCH_JUMP); + InjectHook(0x48E030, RenderScene, PATCH_JUMP); + InjectHook(0x48E080, RenderDebugShit, PATCH_JUMP); + InjectHook(0x48E090, RenderEffects, PATCH_JUMP); + InjectHook(0x48E0E0, Render2dStuff, PATCH_JUMP); + InjectHook(0x48E450, RenderMenus, PATCH_JUMP); + InjectHook(0x48D120, DoFade, PATCH_JUMP); + InjectHook(0x48E470, Render2dStuffAfterFade, PATCH_JUMP); + + InjectHook(0x48D550, LoadSplash, PATCH_JUMP); + InjectHook(0x48D670, DestroySplashScreen, PATCH_JUMP); + InjectHook(0x48D770, LoadingScreen, PATCH_JUMP); + InjectHook(0x48D760, ResetLoadingScreenBar, PATCH_JUMP); + + InjectHook(0x48D470, PluginAttach, PATCH_JUMP); + InjectHook(0x48D520, Initialise3D, PATCH_JUMP); + InjectHook(0x48D540, Terminate3D, PATCH_JUMP); + InjectHook(0x48E800, AppEventHandler, PATCH_JUMP); +ENDPATCHES diff --git a/src/core/main.h b/src/core/main.h new file mode 100644 index 00000000..bdb0e008 --- /dev/null +++ b/src/core/main.h @@ -0,0 +1,22 @@ +#pragma once + +struct GlobalScene +{ + RpWorld *world; + RwCamera *camera; +}; +extern GlobalScene &Scene; + +extern uint8 work_buff[55000]; +extern char gString[256]; +extern wchar *gUString; +extern bool &b_FoundRecentSavedGameWantToLoad; + +class CSprite2d; + +void InitialiseGame(void); +void LoadingScreen(const char *str1, const char *str2, const char *splashscreen); +void LoadingIslandScreen(const char *levelName); +CSprite2d *LoadSplash(const char *name); +char *GetLevelSplashScreen(int level); +char *GetRandomSplashScreen(void); diff --git a/src/core/patcher.cpp b/src/core/patcher.cpp new file mode 100644 index 00000000..5fdbdf8b --- /dev/null +++ b/src/core/patcher.cpp @@ -0,0 +1,22 @@ +#include "common.h" +#include "patcher.h" + +StaticPatcher *StaticPatcher::ms_head; + +StaticPatcher::StaticPatcher(Patcher func) + : m_func(func) +{ + m_next = ms_head; + ms_head = this; +} + +void +StaticPatcher::Apply() +{ + StaticPatcher *current = ms_head; + while(current){ + current->Run(); + current = current->m_next; + } + ms_head = nil; +} diff --git a/src/core/patcher.h b/src/core/patcher.h new file mode 100644 index 00000000..87a6bea4 --- /dev/null +++ b/src/core/patcher.h @@ -0,0 +1,192 @@ +#pragma once + +#define WRAPPER __declspec(naked) +#define DEPRECATED __declspec(deprecated) +#define EAXJMP(a) { _asm mov eax, a _asm jmp eax } +#define VARJMP(a) { _asm jmp a } +#define WRAPARG(a) UNREFERENCED_PARAMETER(a) + +#define NOVMT __declspec(novtable) +#define SETVMT(a) *((DWORD_PTR*)this) = (DWORD_PTR)a + +#include <algorithm> +#include <vector> + +#include "common.h" + +enum +{ + PATCH_CALL, + PATCH_JUMP, + PATCH_NOTHING, +}; + +enum +{ + III_10 = 1, + III_11, + III_STEAM, + VC_10, + VC_11, + VC_STEAM +}; + +extern int gtaversion; + +class StaticPatcher +{ +private: + using Patcher = void(*)(); + + Patcher m_func; + StaticPatcher *m_next; + static StaticPatcher *ms_head; + + void Run() { m_func(); } +public: + StaticPatcher(Patcher func); + static void Apply(); +}; + +template<typename T> +inline T AddressByVersion(uint32_t addressIII10, uint32_t addressIII11, uint32_t addressIIISteam, uint32_t addressvc10, uint32_t addressvc11, uint32_t addressvcSteam) +{ + if(gtaversion == -1){ + if(*(uint32_t*)0x5C1E75 == 0xB85548EC) gtaversion = III_10; + else if(*(uint32_t*)0x5C2135 == 0xB85548EC) gtaversion = III_11; + else if(*(uint32_t*)0x5C6FD5 == 0xB85548EC) gtaversion = III_STEAM; + else if(*(uint32_t*)0x667BF5 == 0xB85548EC) gtaversion = VC_10; + else if(*(uint32_t*)0x667C45 == 0xB85548EC) gtaversion = VC_11; + else if(*(uint32_t*)0x666BA5 == 0xB85548EC) gtaversion = VC_STEAM; + else gtaversion = 0; + } + switch(gtaversion){ + case III_10: + return (T)addressIII10; + case III_11: + return (T)addressIII11; + case III_STEAM: + return (T)addressIIISteam; + case VC_10: + return (T)addressvc10; + case VC_11: + return (T)addressvc11; + case VC_STEAM: + return (T)addressvcSteam; + default: + return (T)0; + } +} + +inline bool +is10(void) +{ + return gtaversion == III_10 || gtaversion == VC_10; +} + +inline bool +isIII(void) +{ + return gtaversion >= III_10 && gtaversion <= III_STEAM; +} + +inline bool +isVC(void) +{ + return gtaversion >= VC_10 && gtaversion <= VC_STEAM; +} + +#define PTRFROMCALL(addr) (uint32_t)(*(uint32_t*)((uint32_t)addr+1) + (uint32_t)addr + 5) +#define INTERCEPT(saved, func, a) \ +{ \ + saved = PTRFROMCALL(a); \ + InjectHook(a, func); \ +} + +template<typename T, typename AT> inline void +Patch(AT address, T value) +{ + DWORD dwProtect[2]; + VirtualProtect((void*)address, sizeof(T), PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(T*)address = value; + VirtualProtect((void*)address, sizeof(T), dwProtect[0], &dwProtect[1]); +} + +template<typename AT> inline void +Nop(AT address, unsigned int nCount) +{ + DWORD dwProtect[2]; + VirtualProtect((void*)address, nCount, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + memset((void*)address, 0x90, nCount); + VirtualProtect((void*)address, nCount, dwProtect[0], &dwProtect[1]); +} + +template<typename AT> inline void +ClearCC(AT address, unsigned int nCount) +{ + DWORD dwProtect[2]; + VirtualProtect((void*)address, nCount, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + memset((void*)address, 0xCC, nCount); + VirtualProtect((void*)address, nCount, dwProtect[0], &dwProtect[1]); +} + +extern std::vector<int32> usedAddresses; + +template<typename AT, typename HT> inline void +InjectHook(AT address, HT hook, unsigned int nType=PATCH_NOTHING) +{ + if(std::any_of(usedAddresses.begin(), usedAddresses.end(), + [address](AT value) { return (int32)value == address; })) { + debug("Used address %#06x twice when injecting hook\n", address); + } + + usedAddresses.push_back((int32)address); + + DWORD dwProtect[2]; + switch ( nType ) + { + case PATCH_JUMP: + VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(BYTE*)address = 0xE9; + break; + case PATCH_CALL: + VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + *(BYTE*)address = 0xE8; + break; + default: + VirtualProtect((void*)((DWORD)address + 1), 4, PAGE_EXECUTE_READWRITE, &dwProtect[0]); + break; + } + DWORD dwHook; + _asm + { + mov eax, hook + mov dwHook, eax + } + + *(ptrdiff_t*)((DWORD)address + 1) = (DWORD)dwHook - (DWORD)address - 5; + if ( nType == PATCH_NOTHING ) + VirtualProtect((void*)((DWORD)address + 1), 4, dwProtect[0], &dwProtect[1]); + else + VirtualProtect((void*)address, 5, dwProtect[0], &dwProtect[1]); +} + +inline void ExtractCall(void *dst, uint32_t a) +{ + *(uint32_t*)dst = (uint32_t)(*(uint32_t*)(a+1) + a + 5); +} +template<typename T> +inline void InterceptCall(void *dst, T func, uint32_t a) +{ + ExtractCall(dst, a); + InjectHook(a, func); +} +template<typename T> +inline void InterceptVmethod(void *dst, T func, uint32_t a) +{ + *(uint32_t*)dst = *(uint32_t*)a; + Patch(a, func); +} + +#define STARTPATCHES static StaticPatcher Patcher([](){ +#define ENDPATCHES }); diff --git a/src/core/re3.cpp b/src/core/re3.cpp new file mode 100644 index 00000000..9dc39d46 --- /dev/null +++ b/src/core/re3.cpp @@ -0,0 +1,381 @@ +#include <direct.h> +#include <csignal> +#include <windows.h> +#include "common.h" +#include "patcher.h" +#include "Renderer.h" +#include "Credits.h" +#include "Camera.h" +#include "Weather.h" +#include "Clock.h" +#include "World.h" +#include "Vehicle.h" +#include "Streaming.h" +#include "PathFind.h" +#include "Boat.h" +#include "Automobile.h" +#include "debugmenu_public.h" + +#include <vector> + +std::vector<int32> usedAddresses; + +void **rwengine = *(void***)0x5A10E1; + +DebugMenuAPI gDebugMenuAPI; + +WRAPPER void *gtanew(uint32 sz) { EAXJMP(0x5A0690); } +WRAPPER void gtadelete(void *p) { EAXJMP(0x5A07E0); } + +// overload our own new/delete with GTA's functions +void *operator new(size_t sz) { return gtanew(sz); } +void operator delete(void *ptr) noexcept { gtadelete(ptr); } + +#ifdef USE_PS2_RAND +unsigned __int64 myrand_seed = 1; +#else +unsigned long int myrand_seed = 1; +#endif + +int +myrand(void) +{ +#ifdef USE_PS2_RAND + // Use our own implementation of rand, stolen from PS2 + myrand_seed = 0x5851F42D4C957F2D * myrand_seed + 1; + return ((myrand_seed >> 32) & 0x7FFFFFFF); +#else + // or original codewarrior rand + myrand_seed = myrand_seed * 1103515245 + 12345; + return((myrand_seed >> 16) & 0x7FFF); +#endif +} + +void +mysrand(unsigned int seed) +{ + myrand_seed = seed; +} + +int (*open_script_orig)(const char *path, const char *mode); +int +open_script(const char *path, const char *mode) +{ + static int scriptToLoad = 1; + + if(GetAsyncKeyState('G') & 0x8000) + scriptToLoad = 0; + if(GetAsyncKeyState('R') & 0x8000) + scriptToLoad = 1; + if(GetAsyncKeyState('D') & 0x8000) + scriptToLoad = 2; + + switch(scriptToLoad){ + case 0: return open_script_orig(path, mode); + case 1: return open_script_orig("main_freeroam.scm", mode); + case 2: return open_script_orig("main_d.scm", mode); + } + return open_script_orig(path, mode); +} + +int gDbgSurf; + +void (*DebugMenuProcess)(void); +void (*DebugMenuRender)(void); +static void stub(void) { } + +void +DebugMenuInit(void) +{ + if(DebugMenuLoad()){ + DebugMenuProcess = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuProcess"); + DebugMenuRender = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuRender"); + } + if(DebugMenuProcess == nil || DebugMenuRender == nil){ + DebugMenuProcess = stub; + DebugMenuRender = stub; + } + +} + +void WeaponCheat(); +void HealthCheat(); +void TankCheat(); +void BlowUpCarsCheat(); +void ChangePlayerCheat(); +void MayhemCheat(); +void EverybodyAttacksPlayerCheat(); +void WeaponsForAllCheat(); +void FastTimeCheat(); +void SlowTimeCheat(); +void MoneyCheat(); +void ArmourCheat(); +void WantedLevelUpCheat(); +void WantedLevelDownCheat(); +void SunnyWeatherCheat(); +void CloudyWeatherCheat(); +void RainyWeatherCheat(); +void FoggyWeatherCheat(); +void FastWeatherCheat(); +void OnlyRenderWheelsCheat(); +void ChittyChittyBangBangCheat(); +void StrongGripCheat(); +void NastyLimbsCheat(); + +// needs too much stuff for now +#if 0 +void +spawnCar(int id) +{ + CVector playerpos; + CStreaming::RequestModel(id, 0); + CStreaming::LoadAllRequestedModels(false); + if(CStreaming::HasModelLoaded(id)){ + FindPlayerCoors(playerpos); + int node = ThePaths.FindNodeClosestToCoors(playerpos, 0, 100.0f, false, false); + if(node < 0) + return; + + CVehicle *v; + if(CModelInfo::IsBoatModel(id)){ +// CBoat* boat = (CBoat*)CVehicle__new(0x484); +// boat = boat->ctor(id, 1); +// v = (CVehicle*)(boat); + }else{ +// CAutomobile *au = (CAutomobile*)CVehicle__new(0x5A8); +// au = au->ctor(id, 1); +// v = (CVehicle*)au; + } +/* + // unlock doors + FIELD(int, v, 0x224) = 1; + // set player owned + FIELD(uint8, v, 0x1F7) |= 4; + + DebugMenuEntrySetAddress(carCol1, &FIELD(uchar, v, 0x19C)); + DebugMenuEntrySetAddress(carCol2, &FIELD(uchar, v, 0x19D)); + //if(id == MODELID_ESPERANTO) + // FIELD(uchar, v, 0x19C) = 54; + + v->matrix.matrix.pos.x = ThePaths.nodes[node].x; + v->matrix.matrix.pos.y = ThePaths.nodes[node].y; + v->matrix.matrix.pos.z = ThePaths.nodes[node].z + 4.0f; + float x = v->matrix.matrix.pos.x; + float y = v->matrix.matrix.pos.y; + float z = v->matrix.matrix.pos.z; + v->matrix.SetRotate(0.0f, 0.0f, 3.49f); + v->matrix.matrix.pos.x += x; + v->matrix.matrix.pos.y += y; + v->matrix.matrix.pos.z += z; + v->bfTypeStatus = v->bfTypeStatus & 7 | 0x20; + FIELD(int, v, 0x224) = 1; +*/ + CWorld::Add(v); + } +} +#endif + +void +DebugMenuPopulate(void) +{ + if(DebugMenuLoad()){ + static const char *weathers[] = { + "Sunny", "Cloudy", "Rainy", "Foggy" + }; + DebugMenuEntry *e; + e = DebugMenuAddVar("Time & Weather", "Current Hour", &CClock::GetHoursRef(), nil, 1, 0, 23, nil); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "Current Minute", &CClock::GetMinutesRef(), + [](){ CWeather::InterpolationValue = CClock::GetMinutes()/60.0f; }, 1, 0, 59, nil); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "Old Weather", (int16*)&CWeather::OldWeatherType, nil, 1, 0, 3, weathers); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "New Weather", (int16*)&CWeather::NewWeatherType, nil, 1, 0, 3, weathers); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Time & Weather", "Wind", (float*)&CWeather::Wind, nil, 0.1f, 0.0f, 1.0f); + DebugMenuAddVar("Time & Weather", "Time scale", (float*)0x8F2C20, nil, 0.1f, 0.0f, 10.0f); + + DebugMenuAddCmd("Cheats", "Weapons", WeaponCheat); + DebugMenuAddCmd("Cheats", "Money", MoneyCheat); + DebugMenuAddCmd("Cheats", "Health", HealthCheat); + DebugMenuAddCmd("Cheats", "Wanted level up", WantedLevelUpCheat); + DebugMenuAddCmd("Cheats", "Wanted level down", WantedLevelDownCheat); + DebugMenuAddCmd("Cheats", "Tank", TankCheat); + DebugMenuAddCmd("Cheats", "Blow up cars", BlowUpCarsCheat); + DebugMenuAddCmd("Cheats", "Change player", ChangePlayerCheat); + DebugMenuAddCmd("Cheats", "Mayhem", MayhemCheat); + DebugMenuAddCmd("Cheats", "Everybody attacks player", EverybodyAttacksPlayerCheat); + DebugMenuAddCmd("Cheats", "Weapons for all", WeaponsForAllCheat); + DebugMenuAddCmd("Cheats", "Fast time", FastTimeCheat); + DebugMenuAddCmd("Cheats", "Slow time", SlowTimeCheat); + DebugMenuAddCmd("Cheats", "Armour", ArmourCheat); + DebugMenuAddCmd("Cheats", "Sunny weather", SunnyWeatherCheat); + DebugMenuAddCmd("Cheats", "Cloudy weather", CloudyWeatherCheat); + DebugMenuAddCmd("Cheats", "Rainy weather", RainyWeatherCheat); + DebugMenuAddCmd("Cheats", "Foggy weather", FoggyWeatherCheat); + DebugMenuAddCmd("Cheats", "Fast weather", FastWeatherCheat); + DebugMenuAddCmd("Cheats", "Only render wheels", OnlyRenderWheelsCheat); + DebugMenuAddCmd("Cheats", "Chitty chitty bang bang", ChittyChittyBangBangCheat); + DebugMenuAddCmd("Cheats", "Strong grip", StrongGripCheat); + DebugMenuAddCmd("Cheats", "Nasty limbs", NastyLimbsCheat); + + DebugMenuAddVarBool8("Debug", "Show Ped Road Groups", (int8*)&gbShowPedRoadGroups, nil); + DebugMenuAddVarBool8("Debug", "Show Car Road Groups", (int8*)&gbShowCarRoadGroups, nil); + DebugMenuAddVarBool8("Debug", "Show Collision Polys", (int8*)&gbShowCollisionPolys, nil); + DebugMenuAddVarBool8("Debug", "Don't render Buildings", (int8*)&gbDontRenderBuildings, nil); + DebugMenuAddVarBool8("Debug", "Don't render Big Buildings", (int8*)&gbDontRenderBigBuildings, nil); + DebugMenuAddVarBool8("Debug", "Don't render Peds", (int8*)&gbDontRenderPeds, nil); + DebugMenuAddVarBool8("Debug", "Don't render Objects", (int8*)&gbDontRenderObjects, nil); + DebugMenuAddVar("Debug", "Dbg Surface", &gDbgSurf, nil, 1, 0, 34, nil); + + DebugMenuAddCmd("Debug", "Start Credits", CCredits::Start); + DebugMenuAddCmd("Debug", "Stop Credits", CCredits::Stop); + } +} + +/* +int (*RsEventHandler_orig)(int a, int b); +int +delayedPatches10(int a, int b) +{ + DebugMenuInit(); + DebugMenuPopulate(); + + return RsEventHandler_orig(a, b); +} +*/ + +void __declspec(naked) HeadlightsFix() +{ + static const float fMinusOne = -1.0f; + _asm + { + fld [esp+708h-690h] + fcomp fMinusOne + fnstsw ax + and ah, 5 + cmp ah, 1 + jnz HeadlightsFix_DontLimit + fld fMinusOne + fstp [esp+708h-690h] + +HeadlightsFix_DontLimit: + fld [esp+708h-690h] + fabs + fld st + push 0x5382F2 + retn + } +} + +const int re3_buffsize = 1024; +static char re3_buff[re3_buffsize]; + +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func) +{ + int nCode; + + strcpy_s(re3_buff, re3_buffsize, "Assertion failed!" ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "File: "); + strcat_s(re3_buff, re3_buffsize, filename ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "Line: " ); + _itoa_s( lineno, re3_buff + strlen(re3_buff), re3_buffsize - strlen(re3_buff), 10 ); + strcat_s(re3_buff, re3_buffsize, "\n"); + + strcat_s(re3_buff, re3_buffsize, "Function: "); + strcat_s(re3_buff, re3_buffsize, func ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "Expression: "); + strcat_s(re3_buff, re3_buffsize, expr); + strcat_s(re3_buff, re3_buffsize, "\n"); + + strcat_s(re3_buff, re3_buffsize, "\n" ); + strcat_s(re3_buff, re3_buffsize, "(Press Retry to debug the application)"); + + + nCode = ::MessageBoxA(nil, re3_buff, "RE3 Assertion Failed!", + MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + if (nCode == IDABORT) + { + raise(SIGABRT); + _exit(3); + } + + if (nCode == IDRETRY) + { + __debugbreak(); + return; + } + + if (nCode == IDIGNORE) + return; + + abort(); +} + +void re3_debug(char *format, ...) +{ + va_list va; + va_start(va, format); + vsprintf_s(re3_buff, re3_buffsize, format, va); + va_end(va); + + printf("%s", re3_buff); +} + +void re3_trace(const char *filename, unsigned int lineno, const char *func, char *format, ...) +{ + char buff[re3_buffsize *2]; + va_list va; + va_start(va, format); + vsprintf_s(re3_buff, re3_buffsize, format, va); + va_end(va); + + sprintf_s(buff, re3_buffsize * 2, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff); + + OutputDebugStringA(buff); +} + +void +patch() +{ + StaticPatcher::Apply(); + +// Patch<float>(0x46BC61+6, 1.0f); // car distance + InjectHook(0x59E460, printf, PATCH_JUMP); + InjectHook(0x475E00, printf, PATCH_JUMP); // _Error + + + // stolen from silentpatch (sorry) + Patch<WORD>(0x5382BF, 0x0EEB); + InjectHook(0x5382EC, HeadlightsFix, PATCH_JUMP); + + InterceptCall(&open_script_orig, open_script, 0x438869); + +// InterceptCall(&RsEventHandler_orig, delayedPatches10, 0x58275E); +} + +BOOL WINAPI +DllMain(HINSTANCE hInst, DWORD reason, LPVOID) +{ + if(reason == DLL_PROCESS_ATTACH){ + + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + if (*(DWORD*)0x5C1E75 == 0xB85548EC) // 1.0 + patch(); + else + return FALSE; + } + + return TRUE; +} diff --git a/src/core/rw.cpp b/src/core/rw.cpp new file mode 100644 index 00000000..52bcf5bb --- /dev/null +++ b/src/core/rw.cpp @@ -0,0 +1,415 @@ +#include "common.h" +#include "patcher.h" +#include "rwcore.h" +#include "rpworld.h" +#include "rpmatfx.h" +#include "rpskin.h" +#include "rphanim.h" +#include "rtbmp.h" + +typedef RwV3d *(*rwVectorsMultFn) (RwV3d * pointsOut, + const RwV3d * pointsIn, + RwInt32 numPoints, + const RwMatrix * matrix); + + +WRAPPER void _rwObjectHasFrameSetFrame(void* object, RwFrame* frame) { EAXJMP(0x5BC950); } +WRAPPER RpAtomic* AtomicDefaultRenderCallBack(RpAtomic* atomic) { EAXJMP(0x59E690); } +WRAPPER void _rpAtomicResyncInterpolatedSphere(RpAtomic* atomic) { EAXJMP(0x59E6C0); } +WRAPPER RwSphere const* RpAtomicGetWorldBoundingSphere(RpAtomic* atomic) { EAXJMP(0x59E800); } +WRAPPER RwInt32 RpClumpGetNumAtomics(RpClump* clump) { EAXJMP(0x59ED50); } +WRAPPER RpClump* RpClumpRender(RpClump* clump) { EAXJMP(0x59ED80); } +WRAPPER RpClump* RpClumpForAllAtomics(RpClump* clump, RpAtomicCallBack callback, void* pData) { EAXJMP(0x59EDD0); } +WRAPPER RpClump* RpClumpForAllCameras(RpClump* clump, RwCameraCallBack callback, void* pData) { EAXJMP(0x59EE10); } +WRAPPER RpClump* RpClumpForAllLights(RpClump* clump, RpLightCallBack callback, void* pData) { EAXJMP(0x59EE60); } +WRAPPER RpAtomic* RpAtomicCreate() { EAXJMP(0x59EEB0); } +WRAPPER RpAtomic* RpAtomicSetGeometry(RpAtomic* atomic, RpGeometry* geometry, RwUInt32 flags) { EAXJMP(0x59EFA0); } +WRAPPER RwBool RpAtomicDestroy(RpAtomic* atomic) { EAXJMP(0x59F020); } +WRAPPER RpAtomic* RpAtomicClone(RpAtomic* atomic) { EAXJMP(0x59F0A0); } +WRAPPER RpClump* RpClumpClone(RpClump* clump) { EAXJMP(0x59F1B0); } +WRAPPER RpClump* RpClumpCreate() { EAXJMP(0x59F490); } +WRAPPER RwBool RpClumpDestroy(RpClump* clump) { EAXJMP(0x59F500); } +WRAPPER RpClump* RpClumpAddAtomic(RpClump* clump, RpAtomic* atomic) { EAXJMP(0x59F680); } +WRAPPER RpClump* RpClumpRemoveAtomic(RpClump* clump, RpAtomic* atomic) { EAXJMP(0x59F6B0); } +WRAPPER RpClump* RpClumpRemoveLight(RpClump* clump, RpLight* light) { EAXJMP(0x59F6E0); } +WRAPPER RpClump* RpClumpStreamRead(RwStream* stream) { EAXJMP(0x59FC50); } +WRAPPER RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A0510); } +WRAPPER RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A0540); } +WRAPPER RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5A0570); } +WRAPPER RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB) { EAXJMP(0x5A05A0); } +WRAPPER RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB) { EAXJMP(0x5A05C0); } +WRAPPER RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID) { EAXJMP(0x5A05E0); } +WRAPPER RpAtomic* RpAtomicSetFrame(RpAtomic* atomic, RwFrame* frame) { EAXJMP(0x5A0600); } +WRAPPER RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB) { EAXJMP(0x5A0DC0); } +WRAPPER RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID) { EAXJMP(0x5A0DF0); } +WRAPPER RwInt32 RwEngineGetNumSubSystems() { EAXJMP(0x5A0E10); } +WRAPPER RwSubSystemInfo* RwEngineGetSubSystemInfo(RwSubSystemInfo* subSystemInfo, RwInt32 subSystemIndex) { EAXJMP(0x5A0E40); } +WRAPPER RwInt32 RwEngineGetCurrentSubSystem() { EAXJMP(0x5A0E70); } +WRAPPER RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex) { EAXJMP(0x5A0EA0); } +WRAPPER RwInt32 RwEngineGetNumVideoModes() { EAXJMP(0x5A0ED0); } +WRAPPER RwVideoMode* RwEngineGetVideoModeInfo(RwVideoMode* modeinfo, RwInt32 modeIndex) { EAXJMP(0x5A0F00); } +WRAPPER RwInt32 RwEngineGetCurrentVideoMode() { EAXJMP(0x5A0F30); } +WRAPPER RwBool RwEngineSetVideoMode(RwInt32 modeIndex) { EAXJMP(0x5A0F60); } +WRAPPER RwBool RwEngineStop() { EAXJMP(0x5A0F90); } +WRAPPER RwBool RwEngineStart() { EAXJMP(0x5A0FE0); } +WRAPPER RwBool RwEngineClose() { EAXJMP(0x5A1070); } +WRAPPER RwBool RwEngineOpen(RwEngineOpenParams* initParams) { EAXJMP(0x5A10E0); } +WRAPPER RwBool RwEngineTerm() { EAXJMP(0x5A1290); } +WRAPPER RwBool RwEngineInit(RwMemoryFunctions* memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize) { EAXJMP(0x5A12D0); } +WRAPPER void* _rwFrameOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A15E0); } +WRAPPER void* _rwFrameClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A1650); } +WRAPPER RwFrame* _rwFrameCloneAndLinkClones(RwFrame* root) { EAXJMP(0x5A1690); } +WRAPPER RwFrame* _rwFramePurgeClone(RwFrame* root) { EAXJMP(0x5A1880); } +WRAPPER RwBool RwFrameDirty(RwFrame const* frame) { EAXJMP(0x5A1930); } +WRAPPER void _rwFrameInit(RwFrame* frame) { EAXJMP(0x5A1950); } +WRAPPER RwFrame* RwFrameCreate() { EAXJMP(0x5A1A00); } +WRAPPER RwBool RwFrameDestroy(RwFrame* frame) { EAXJMP(0x5A1A30); } +WRAPPER RwBool RwFrameDestroyHierarchy(RwFrame* frame) { EAXJMP(0x5A1BF0); } +WRAPPER RwFrame* RwFrameUpdateObjects(RwFrame* frame) { EAXJMP(0x5A1C60); } +WRAPPER RwMatrix* RwFrameGetLTM(RwFrame* frame) { EAXJMP(0x5A1CE0); } +WRAPPER RwFrame* RwFrameAddChild(RwFrame* parent, RwFrame* child) { EAXJMP(0x5A1D00); } +WRAPPER RwFrame* RwFrameRemoveChild(RwFrame* child) { EAXJMP(0x5A1ED0); } +WRAPPER RwFrame* RwFrameForAllChildren(RwFrame* frame, RwFrameCallBack callBack, void* data) { EAXJMP(0x5A1FC0); } +WRAPPER RwFrame* RwFrameTranslate(RwFrame* frame, RwV3d const* v, RwOpCombineType combine) { EAXJMP(0x5A2000); } +WRAPPER RwFrame* RwFrameScale(RwFrame* frame, RwV3d const* v, RwOpCombineType combine) { EAXJMP(0x5A20A0); } +WRAPPER RwFrame* RwFrameTransform(RwFrame* frame, RwMatrix const* m, RwOpCombineType combine) { EAXJMP(0x5A2140); } +WRAPPER RwFrame* RwFrameRotate(RwFrame* frame, RwV3d const* axis, RwReal angle, RwOpCombineType combine) { EAXJMP(0x5A21E0); } +WRAPPER RwFrame* RwFrameSetIdentity(RwFrame* frame) { EAXJMP(0x5A2280); } +WRAPPER RwFrame* RwFrameForAllObjects(RwFrame* frame, RwObjectCallBack callBack, void* data) { EAXJMP(0x5A2340); } +WRAPPER RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A2380); } +WRAPPER RwBool _rwMatrixSetMultFn(rwMatrixMultFn multMat) { EAXJMP(0x5A23B0); } +WRAPPER RwReal _rwMatrixDeterminant(RwMatrix const* matrix) { EAXJMP(0x5A2520); } +WRAPPER RwReal _rwMatrixOrthogonalError(RwMatrix const* matrix) { EAXJMP(0x5A2570); } +WRAPPER RwReal _rwMatrixNormalError(RwMatrix const* matrix) { EAXJMP(0x5A25D0); } +WRAPPER RwReal _rwMatrixIdentityError(RwMatrix const* matrix) { EAXJMP(0x5A2660); } +WRAPPER void* _rwMatrixClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A2730); } +WRAPPER void* _rwMatrixOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A2770); } +WRAPPER RwMatrix* RwMatrixOptimize(RwMatrix* matrix, RwMatrixTolerance const* tolerance) { EAXJMP(0x5A2820); } +WRAPPER RwMatrix* RwMatrixUpdate(RwMatrix* matrix) { EAXJMP(0x5A28E0); } +WRAPPER RwMatrix* RwMatrixMultiply(RwMatrix* matrixOut, RwMatrix const* MatrixIn1, RwMatrix const* matrixIn2) { EAXJMP(0x5A28F0); } +WRAPPER RwMatrix* RwMatrixRotateOneMinusCosineSine(RwMatrix* matrix, RwV3d const* unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp) { EAXJMP(0x5A2960); } +WRAPPER RwMatrix* RwMatrixRotate(RwMatrix* matrix, RwV3d const* axis, RwReal angle, RwOpCombineType combineOp) { EAXJMP(0x5A2BF0); } +WRAPPER RwMatrix* RwMatrixInvert(RwMatrix* matrixOut, RwMatrix const* matrixIn) { EAXJMP(0x5A2C90); } +WRAPPER RwMatrix* RwMatrixScale(RwMatrix* matrix, RwV3d const* scale, RwOpCombineType combineOp) { EAXJMP(0x5A2EE0); } +WRAPPER RwMatrix* RwMatrixTranslate(RwMatrix* matrix, RwV3d const* translation, RwOpCombineType combineOp) { EAXJMP(0x5A3070); } +WRAPPER RwMatrix* RwMatrixTransform(RwMatrix* matrix, RwMatrix const* transform, RwOpCombineType combineOp) { EAXJMP(0x5A31C0); } +WRAPPER RwBool RwMatrixDestroy(RwMatrix* mpMat) { EAXJMP(0x5A3300); } +WRAPPER RwMatrix* RwMatrixCreate() { EAXJMP(0x5A3330); } +WRAPPER RwBool _rwVectorSetMultFn(rwVectorMultFn multPoint, rwVectorsMultFn multPoints, rwVectorMultFn multVector, rwVectorsMultFn multVectors) { EAXJMP(0x5A3450); } +WRAPPER RwReal _rwV3dNormalize(RwV3d* out, RwV3d const* in) { EAXJMP(0x5A3600); } +WRAPPER RwReal RwV3dLength(RwV3d const* in) { EAXJMP(0x5A36A0); } +WRAPPER RwReal _rwSqrt(RwReal const num) { EAXJMP(0x5A3710); } +WRAPPER RwReal _rwInvSqrt(RwReal const num) { EAXJMP(0x5A3770); } +WRAPPER RwV3d* RwV3dTransformPoints(RwV3d* pointsOut, RwV3d const* pointsIn, RwInt32 numPoints, RwMatrix const* matrix) { EAXJMP(0x5A37D0); } +WRAPPER RwV3d* RwV3dTransformVectors(RwV3d* vectorsOut, RwV3d const* vectorsIn, RwInt32 numPoints, RwMatrix const* matrix) { EAXJMP(0x5A37E0); } +WRAPPER void* _rwVectorClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A37F0); } +WRAPPER void* _rwVectorOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5A3860); } +WRAPPER RwUInt32 RwStreamRead(RwStream* stream, void* buffer, RwUInt32 length) { EAXJMP(0x5A3AD0); } +WRAPPER RwStream* RwStreamWrite(RwStream* stream, void const* buffer, RwUInt32 length) { EAXJMP(0x5A3C30); } +WRAPPER RwStream* RwStreamSkip(RwStream* stream, RwUInt32 offset) { EAXJMP(0x5A3DF0); } +WRAPPER RwBool RwStreamClose(RwStream* stream, void* pData) { EAXJMP(0x5A3F10); } +WRAPPER RwStream* RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, void const* pData) { EAXJMP(0x5A3FE0); } +WRAPPER RwReal RwIm2DGetNearScreenZ() { EAXJMP(0x5A43A0); } +WRAPPER RwReal RwIm2DGetFarScreenZ() { EAXJMP(0x5A43B0); } +WRAPPER RwBool RwRenderStateSet(RwRenderState state, void* value) { EAXJMP(0x5A43C0); } +WRAPPER RwBool RwRenderStateGet(RwRenderState state, void* value) { EAXJMP(0x5A4410); } +WRAPPER RwBool RwIm2DRenderLine(RwIm2DVertex* vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2) { EAXJMP(0x5A4420); } +WRAPPER RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex* vertices, RwInt32 numVertices) { EAXJMP(0x5A4430); } +WRAPPER RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex* vertices, RwInt32 numVertices, RwImVertexIndex* indices, RwInt32 numIndices) { EAXJMP(0x5A4440); } +WRAPPER RwCamera* RwCameraEndUpdate(RwCamera* camera) { EAXJMP(0x5A5020); } +WRAPPER RwCamera* RwCameraBeginUpdate(RwCamera* camera) { EAXJMP(0x5A5030); } +WRAPPER RwCamera* RwCameraSetViewOffset(RwCamera* camera, RwV2d const* offset) { EAXJMP(0x5A5040); } +WRAPPER RwCamera* RwCameraSetNearClipPlane(RwCamera* camera, RwReal nearClip) { EAXJMP(0x5A5070); } +WRAPPER RwCamera* RwCameraSetFarClipPlane(RwCamera* camera, RwReal farClip) { EAXJMP(0x5A5140); } +WRAPPER RwFrustumTestResult RwCameraFrustumTestSphere(RwCamera const* camera, RwSphere const* sphere) { EAXJMP(0x5A5170); } +WRAPPER RwCamera* RwCameraClear(RwCamera* camera, RwRGBA* colour, RwInt32 clearMode) { EAXJMP(0x5A51E0); } +WRAPPER RwCamera* RwCameraShowRaster(RwCamera* camera, void* pDev, RwUInt32 flags) { EAXJMP(0x5A5210); } +WRAPPER RwCamera* RwCameraSetProjection(RwCamera* camera, RwCameraProjection projection) { EAXJMP(0x5A5240); } +WRAPPER RwCamera* RwCameraSetViewWindow(RwCamera* camera, RwV2d const* viewWindow) { EAXJMP(0x5A52B0); } +WRAPPER RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5A52F0); } +WRAPPER RwBool RwCameraDestroy(RwCamera* camera) { EAXJMP(0x5A5320); } +WRAPPER RwCamera* RwCameraCreate() { EAXJMP(0x5A5360); } +WRAPPER RwBool RwTextureSetMipmapping(RwBool enable) { EAXJMP(0x5A7100); } +WRAPPER RwBool RwTextureGetMipmapping() { EAXJMP(0x5A7120); } +WRAPPER RwBool RwTextureSetAutoMipmapping(RwBool enable) { EAXJMP(0x5A7130); } +WRAPPER RwBool RwTextureGetAutoMipmapping() { EAXJMP(0x5A7150); } +WRAPPER RwTexDictionary* RwTexDictionaryCreate() { EAXJMP(0x5A7160); } +WRAPPER RwBool RwTexDictionaryDestroy(RwTexDictionary* dict) { EAXJMP(0x5A7200); } +WRAPPER RwTexDictionary const* RwTexDictionaryForAllTextures(RwTexDictionary const* dict, RwTextureCallBack fpCallBack, void* pData) { EAXJMP(0x5A7290); } +WRAPPER RwTexture* RwTextureCreate(RwRaster* raster) { EAXJMP(0x5A72D0); } +WRAPPER RwBool RwTextureDestroy(RwTexture* texture) { EAXJMP(0x5A7330); } +WRAPPER RwTexture* RwTextureSetName(RwTexture* texture, RwChar const* name) { EAXJMP(0x5A73B0); } +WRAPPER RwTexture* RwTextureSetMaskName(RwTexture* texture, RwChar const* maskName) { EAXJMP(0x5A7420); } +WRAPPER RwTexture* RwTexDictionaryAddTexture(RwTexDictionary* dict, RwTexture* texture) { EAXJMP(0x5A7490); } +WRAPPER RwTexture* RwTexDictionaryFindNamedTexture(RwTexDictionary* dict, RwChar const* name) { EAXJMP(0x5A74D0); } +WRAPPER RwTexDictionary* RwTexDictionarySetCurrent(RwTexDictionary* dict) { EAXJMP(0x5A7550); } +WRAPPER RwTexDictionary* RwTexDictionaryGetCurrent() { EAXJMP(0x5A7570); } +WRAPPER RwTexture* RwTextureRead(RwChar const* name, RwChar const* maskName) { EAXJMP(0x5A7580); } +WRAPPER RwBool RwTextureRasterGenerateMipmaps(RwRaster* raster, RwImage* image) { EAXJMP(0x5A7780); } +WRAPPER RwImage* RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth) { EAXJMP(0x5A9120); } +WRAPPER RwBool RwImageDestroy(RwImage* image) { EAXJMP(0x5A9180); } +WRAPPER RwImage* RwImageAllocatePixels(RwImage* image) { EAXJMP(0x5A91E0); } +WRAPPER RwImage* RwImageFreePixels(RwImage* image) { EAXJMP(0x5A92A0); } +WRAPPER RwImage* RwImageMakeMask(RwImage* image) { EAXJMP(0x5A92D0); } +WRAPPER RwImage* RwImageApplyMask(RwImage* image, RwImage const* mask) { EAXJMP(0x5A93A0); } +WRAPPER RwChar const* RwImageSetPath(RwChar const* path) { EAXJMP(0x5A9750); } +WRAPPER RwImage* RwImageRead(RwChar const* imageName) { EAXJMP(0x5A9810); } +WRAPPER RwChar const* RwImageFindFileType(RwChar const* imageName) { EAXJMP(0x5A9B40); } +WRAPPER RwImage* RwImageReadMaskedImage(RwChar const* imageName, RwChar const* maskname) { EAXJMP(0x5A9C10); } +WRAPPER RwImage* RwImageCopy(RwImage* destImage, RwImage const* sourceImage) { EAXJMP(0x5A9F50); } +WRAPPER RwImage* RwImageGammaCorrect(RwImage* image) { EAXJMP(0x5AA130); } +WRAPPER RwBool RwImageSetGamma(RwReal gammaValue) { EAXJMP(0x5AA2C0); } +WRAPPER RwStream* _rwStreamWriteVersionedChunkHeader(RwStream* stream, RwInt32 type, RwInt32 size, RwUInt32 version, RwUInt32 buildNum) { EAXJMP(0x5AA4E0); } +WRAPPER RwBool RwStreamFindChunk(RwStream* stream, RwUInt32 type, RwUInt32* lengthOut, RwUInt32* versionOut) { EAXJMP(0x5AA540); } +WRAPPER void* RwMemLittleEndian32(void* mem, RwUInt32 size) { EAXJMP(0x5AA640); } +WRAPPER void* RwMemNative32(void* mem, RwUInt32 size) { EAXJMP(0x5AA650); } +WRAPPER void* RwMemFloat32ToReal(void* mem, RwUInt32 size) { EAXJMP(0x5AA660); } +WRAPPER RwStream* RwStreamWriteReal(RwStream* stream, RwReal const* reals, RwUInt32 numBytes) { EAXJMP(0x5AA680); } +WRAPPER RwStream* RwStreamWriteInt32(RwStream* stream, RwInt32 const* ints, RwUInt32 numBytes) { EAXJMP(0x5AA720); } +WRAPPER RwStream* RwStreamReadReal(RwStream* stream, RwReal* reals, RwUInt32 numBytes) { EAXJMP(0x5AA740); } +WRAPPER RwStream* RwStreamReadInt32(RwStream* stream, RwInt32* ints, RwUInt32 numBytes) { EAXJMP(0x5AA7B0); } +WRAPPER RwUInt32 RwTextureStreamGetSize(RwTexture const* texture) { EAXJMP(0x5AA800); } +WRAPPER RwTexture const* RwTextureStreamWrite(RwTexture const* texture, RwStream* stream) { EAXJMP(0x5AA870); } +WRAPPER RwTexture* RwTextureStreamRead(RwStream* stream) { EAXJMP(0x5AAA40); } +WRAPPER RwTexDictionary const* RwTexDictionaryStreamWrite(RwTexDictionary const* texDict, RwStream* stream) { EAXJMP(0x5AB020); } +WRAPPER RpMorphTarget const* RpMorphTargetCalcBoundingSphere(RpMorphTarget const* morphTarget, RwSphere* boundingSphere) { EAXJMP(0x5AC890); } +WRAPPER RwInt32 RpGeometryAddMorphTargets(RpGeometry* geometry, RwInt32 mtcount) { EAXJMP(0x5AC9A0); } +WRAPPER RpGeometry const* RpGeometryTriangleSetVertexIndices(RpGeometry const* geometry, RpTriangle* triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3) { EAXJMP(0x5ACB60); } +WRAPPER RpGeometry* RpGeometryTriangleSetMaterial(RpGeometry* geometry, RpTriangle* triangle, RpMaterial* material) { EAXJMP(0x5ACB90); } +WRAPPER RpGeometry* RpGeometryForAllMaterials(RpGeometry* geometry, RpMaterialCallBack fpCallBack, void* pData) { EAXJMP(0x5ACBF0); } +WRAPPER RpGeometry* RpGeometryLock(RpGeometry* geometry, RwInt32 lockMode) { EAXJMP(0x5ACC30); } +WRAPPER RpGeometry* RpGeometryUnlock(RpGeometry* geometry) { EAXJMP(0x5ACC60); } +WRAPPER RpGeometry* RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format) { EAXJMP(0x5ACD10); } +WRAPPER RpGeometry* _rpGeometryAddRef(RpGeometry* geometry) { EAXJMP(0x5ACF40); } +WRAPPER RwBool RpGeometryDestroy(RpGeometry* geometry) { EAXJMP(0x5ACF50); } +WRAPPER RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5ACFF0); } +WRAPPER RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5AD020); } +WRAPPER RpGeometry* RpGeometryStreamRead(RwStream* stream) { EAXJMP(0x5AD050); } +WRAPPER RwRaster* RwRasterGetCurrentContext() { EAXJMP(0x5AD6D0); } +WRAPPER RwRaster* RwRasterUnlock(RwRaster* raster) { EAXJMP(0x5AD6F0); } +WRAPPER RwRaster* RwRasterRenderFast(RwRaster* raster, RwInt32 x, RwInt32 y) { EAXJMP(0x5AD710); } +WRAPPER RwRaster* RwRasterUnlockPalette(RwRaster* raster) { EAXJMP(0x5AD750); } +WRAPPER RwBool RwRasterDestroy(RwRaster* raster) { EAXJMP(0x5AD780); } +WRAPPER RwRaster* RwRasterPushContext(RwRaster* raster) { EAXJMP(0x5AD7C0); } +WRAPPER RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5AD810); } +WRAPPER RwUInt8* RwRasterLockPalette(RwRaster* raster, RwInt32 lockMode) { EAXJMP(0x5AD840); } +WRAPPER RwRaster* RwRasterPopContext() { EAXJMP(0x5AD870); } +WRAPPER RwInt32 RwRasterGetNumLevels(RwRaster* raster) { EAXJMP(0x5AD8C0); } +WRAPPER RwRaster* RwRasterShowRaster(RwRaster* raster, void* dev, RwUInt32 flags) { EAXJMP(0x5AD900); } +WRAPPER RwRaster* RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags) { EAXJMP(0x5AD930); } +WRAPPER RwUInt8* RwRasterLock(RwRaster* raster, RwUInt8 level, RwInt32 lockMode) { EAXJMP(0x5AD9D0); } +WRAPPER RpMaterial* RpMaterialCreate() { EAXJMP(0x5ADC30); } +WRAPPER RwBool RpMaterialDestroy(RpMaterial* material) { EAXJMP(0x5ADCB0); } +WRAPPER RpMaterial* RpMaterialSetTexture(RpMaterial* material, RwTexture* texture) { EAXJMP(0x5ADD10); } +WRAPPER RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5ADD40); } +WRAPPER RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5ADD70); } +WRAPPER RpMaterial* RpMaterialStreamRead(RwStream* stream) { EAXJMP(0x5ADDA0); } +WRAPPER RpWorldSector* _rpSectorDefaultRenderCallBack(RpWorldSector* sector) { EAXJMP(0x5AE0B0); } +WRAPPER RwBool _rpWorldForAllGlobalLights(RpLightCallBack callBack, void* pData) { EAXJMP(0x5AE100); } +WRAPPER RpWorldSector* _rpWorldSectorForAllLocalLights(RpWorldSector* sector, RpLightCallBack callBack, void* pData) { EAXJMP(0x5AE150); } +WRAPPER RpWorld* RpWorldUnlock(RpWorld* world) { EAXJMP(0x5AE190); } +WRAPPER RpWorld* RpWorldSectorGetWorld(RpWorldSector const* sector) { EAXJMP(0x5AE2B0); } +WRAPPER RwBool RpWorldDestroy(RpWorld* world) { EAXJMP(0x5AE340); } +WRAPPER RpWorld* RpWorldCreate(RwBBox* boundingBox) { EAXJMP(0x5AE6A0); } +WRAPPER RwInt32 RpWorldRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5AEA40); } +WRAPPER RwInt32 RpWorldRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5AEA70); } +WRAPPER RwBool RpWorldPluginAttach() { EAXJMP(0x5AEAA0); } +WRAPPER RpWorld* RpWorldAddCamera(RpWorld* world, RwCamera* camera) { EAXJMP(0x5AFB80); } +WRAPPER RpWorld* RpWorldRemoveCamera(RpWorld* world, RwCamera* camera) { EAXJMP(0x5AFBB0); } +WRAPPER RpWorld* RpAtomicGetWorld(RpAtomic const* atomic) { EAXJMP(0x5AFC10); } +WRAPPER RpWorld* RpWorldAddClump(RpWorld* world, RpClump* clump) { EAXJMP(0x5AFC20); } +WRAPPER RpWorld* RpWorldAddLight(RpWorld* world, RpLight* light) { EAXJMP(0x5AFDA0); } +WRAPPER RpWorld* RpWorldRemoveLight(RpWorld* world, RpLight* light) { EAXJMP(0x5AFDF0); } +WRAPPER RwImage* RtBMPImageRead(RwChar const* imageName) { EAXJMP(0x5AFE70); } +WRAPPER RwBool RpSkinPluginAttach() { EAXJMP(0x5B07D0); } +WRAPPER RpAtomic* RpSkinAtomicSetHAnimHierarchy(RpAtomic* atomic, RpHAnimHierarchy* hierarchy) { EAXJMP(0x5B1050); } +WRAPPER RpHAnimHierarchy* RpSkinAtomicGetHAnimHierarchy(RpAtomic const* atomic) { EAXJMP(0x5B1070); } +WRAPPER RpSkin* RpSkinGeometryGetSkin(RpGeometry* geometry) { EAXJMP(0x5B1080); } +WRAPPER RpGeometry* RpSkinGeometrySetSkin(RpGeometry* geometry, RpSkin* skin) { EAXJMP(0x5B1090); } +WRAPPER RwMatrix const* RpSkinGetSkinToBoneMatrices(RpSkin* skin) { EAXJMP(0x5B10D0); } +WRAPPER RpHAnimHierarchy* RpHAnimHierarchyCreate(RwInt32 numNodes, RwUInt32* nodeFlags, RwInt32* nodeIDs, RpHAnimHierarchyFlag flags, RwInt32 maxKeyFrameSize) { EAXJMP(0x5B10E0); } +WRAPPER RpHAnimHierarchy* RpHAnimFrameGetHierarchy(RwFrame* frame) { EAXJMP(0x5B11F0); } +WRAPPER RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy* hierarchy, RpHAnimAnimation* anim) { EAXJMP(0x5B1200); } +WRAPPER RwBool RpHAnimHierarchySubAnimTime(RpHAnimHierarchy* hierarchy, RwReal time) { EAXJMP(0x5B12B0); } +WRAPPER RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy* hierarchy, RwReal time) { EAXJMP(0x5B1480); } +WRAPPER RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy* hierarchy) { EAXJMP(0x5B1780); } +WRAPPER RpHAnimAnimation* RpHAnimAnimationStreamRead(RwStream* stream) { EAXJMP(0x5B1C10); } +WRAPPER RwBool RpHAnimPluginAttach() { EAXJMP(0x5B1D50); } +WRAPPER RwBool RpMatFXPluginAttach() { EAXJMP(0x5B2640); } +WRAPPER RpAtomic* RpMatFXAtomicEnableEffects(RpAtomic* atomic) { EAXJMP(0x5B3750); } +WRAPPER RpMaterial* RpMatFXMaterialSetEffects(RpMaterial* material, RpMatFXMaterialFlags flags) { EAXJMP(0x5B3780); } +WRAPPER RpMaterial* RpMatFXMaterialSetupEnvMap(RpMaterial* material, RwTexture* texture, RwFrame* frame, RwBool useFrameBufferAlpha, RwReal coef) { EAXJMP(0x5B38D0); } +WRAPPER RpMaterial* RpMatFXMaterialSetBumpMapTexture(RpMaterial* material, RwTexture* texture) { EAXJMP(0x5B3A40); } +WRAPPER RwBool RwD3D8SetRenderState(RwUInt32 state, RwUInt32 value) { EAXJMP(0x5B3CF0); } +WRAPPER void RwD3D8GetRenderState(RwUInt32 state, void* value) { EAXJMP(0x5B3D40); } +WRAPPER RwBool RwD3D8SetTextureStageState(RwUInt32 stage, RwUInt32 type, RwUInt32 value) { EAXJMP(0x5B3D60); } +WRAPPER RwBool RwD3D8SetTexture(RwTexture* texture, RwUInt32 stage) { EAXJMP(0x5B53A0); } +WRAPPER void* RwIm3DTransform(RwIm3DVertex* pVerts, RwUInt32 numVerts, RwMatrix* ltm, RwUInt32 flags) { EAXJMP(0x5B6720); } +WRAPPER RwBool RwIm3DEnd() { EAXJMP(0x5B67F0); } +WRAPPER RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex* indices, RwInt32 numIndices) { EAXJMP(0x5B6820); } +WRAPPER RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2) { EAXJMP(0x5B6980); } +WRAPPER RxPipeline* RwIm3DSetTransformPipeline(RxPipeline* pipeline) { EAXJMP(0x5B6A50); } +WRAPPER RxPipeline* RwIm3DSetRenderPipeline(RxPipeline* pipeline, RwPrimitiveType primType) { EAXJMP(0x5B6AC0); } +WRAPPER void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate) { EAXJMP(0x5B95D0); } +WRAPPER RwBool RwD3D8CameraAttachWindow(void* camera, void* hwnd) { EAXJMP(0x5B9640); } +WRAPPER RwBool RwD3D8DeviceSupportsDXTTexture() { EAXJMP(0x5BAEB0); } +WRAPPER RwBool RwD3D8SetVertexShader(RwUInt32 handle) { EAXJMP(0x5BAF90); } +WRAPPER RwBool RwD3D8SetPixelShader(RwUInt32 handle) { EAXJMP(0x5BAFD0); } +WRAPPER RwBool RwD3D8SetStreamSource(RwUInt32 streamNumber, void* streamData, RwUInt32 stride) { EAXJMP(0x5BB010); } +WRAPPER RwBool RwD3D8SetIndices(void* indexData, RwUInt32 baseVertexIndex) { EAXJMP(0x5BB060); } +WRAPPER RwBool RwD3D8DrawIndexedPrimitive(RwUInt32 primitiveType, RwUInt32 minIndex, RwUInt32 numVertices, RwUInt32 startIndex, RwUInt32 numIndices) { EAXJMP(0x5BB0B0); } +WRAPPER RwBool RwD3D8DrawPrimitive(RwUInt32 primitiveType, RwUInt32 startVertex, RwUInt32 numVertices) { EAXJMP(0x5BB140); } +WRAPPER RwBool RwD3D8SetTransform(RwUInt32 state, void const* matrix) { EAXJMP(0x5BB1D0); } +WRAPPER void RwD3D8GetTransform(RwUInt32 state, void* matrix) { EAXJMP(0x5BB310); } +WRAPPER RwBool RwD3D8SetTransformWorld(RwMatrix const* matrix) { EAXJMP(0x5BB340); } +WRAPPER RwBool RwD3D8SetSurfaceProperties(RwRGBA const* color, RwSurfaceProperties const* surfaceProps, RwBool modulate) { EAXJMP(0x5BB490); } +WRAPPER RwBool RwD3D8SetLight(RwInt32 index, void const* light) { EAXJMP(0x5BB7A0); } +WRAPPER RwBool RwD3D8EnableLight(RwInt32 index, RwBool enable) { EAXJMP(0x5BB890); } +WRAPPER RwBool RwD3D8DynamicVertexBufferCreate(RwUInt32 fvf, RwUInt32 size, void** vertexBuffer) { EAXJMP(0x5BB9F0); } +WRAPPER void RwD3D8DynamicVertexBufferDestroy(void* vertexBuffer) { EAXJMP(0x5BBAE0); } +WRAPPER RwBool RwD3D8IndexBufferCreate(RwUInt32 numIndices, void** indexBuffer) { EAXJMP(0x5BBB10); } +WRAPPER RwBool RwD3D8CreatePixelShader(RwUInt32 const* function, RwUInt32* handle) { EAXJMP(0x5BBB40); } +WRAPPER void RwD3D8DeletePixelShader(RwUInt32 handle) { EAXJMP(0x5BBB90); } +WRAPPER RwBool RwD3D8SetPixelShaderConstant(RwUInt32 registerAddress, void const* antData, RwUInt32 antCount) { EAXJMP(0x5BBC00); } +WRAPPER void const* RwD3D8GetCaps() { EAXJMP(0x5BBC30); } +WRAPPER RwBool RwD3D8CameraIsSphereFullyInsideFrustum(void const* camera, void const* sphere) { EAXJMP(0x5BBC40); } +WRAPPER RwBool RwD3D8CameraIsBBoxFullyInsideFrustum(void const* camera, void const* boundingBox) { EAXJMP(0x5BBCA0); } +WRAPPER RwBool RwD3D8DynamicVertexBufferLock(RwUInt32 vertexSize, RwUInt32 numVertex, void** vertexBufferOut, void** vertexDataOut, RwUInt32* baseIndexOut) { EAXJMP(0x5BBD30); } +WRAPPER RwBool RwD3D8DynamicVertexBufferUnlock(void* vertexBuffer) { EAXJMP(0x5BBEB0); } +WRAPPER RwBool _rwIntelSSEsupported() { EAXJMP(0x5BBED0); } +WRAPPER RwImage* RwImageSetFromRaster(RwImage* image, RwRaster* raster) { EAXJMP(0x5BBF10); } +WRAPPER RwRaster* RwRasterSetFromImage(RwRaster* raster, RwImage* image) { EAXJMP(0x5BBF50); } +WRAPPER RwImage* RwImageFindRasterFormat(RwImage* ipImage, RwInt32 nRasterType, RwInt32* npWidth, RwInt32* npHeight, RwInt32* npDepth, RwInt32* npFormat) { EAXJMP(0x5BBF80); } +WRAPPER RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5BBFF0); } +WRAPPER rwFrameList* _rwFrameListDeinitialize(rwFrameList* frameList) { EAXJMP(0x5BC020); } +WRAPPER rwFrameList* _rwFrameListStreamRead(RwStream* stream, rwFrameList* fl) { EAXJMP(0x5BC050); } +WRAPPER RpLight* RpLightSetRadius(RpLight* light, RwReal radius) { EAXJMP(0x5BC300); } +WRAPPER RpLight* RpLightSetColor(RpLight* light, RwRGBAReal const* color) { EAXJMP(0x5BC320); } +WRAPPER RwReal RpLightGetConeAngle(RpLight const* light) { EAXJMP(0x5BC370); } +WRAPPER RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5BC5B0); } +WRAPPER RpLight* RpLightStreamRead(RwStream* stream) { EAXJMP(0x5BC5E0); } +WRAPPER RwBool RpLightDestroy(RpLight* light) { EAXJMP(0x5BC780); } +WRAPPER RpLight* RpLightCreate(RwInt32 type) { EAXJMP(0x5BC7C0); } +WRAPPER void _rwD3D8TexDictionaryEnableRasterFormatConversion(RwBool enable) { EAXJMP(0x5BE280); } +WRAPPER RwFileFunctions* RwOsGetFileInterface() { EAXJMP(0x5BF110); } +WRAPPER RwBool RwFreeListDestroy(RwFreeList* freelist) { EAXJMP(0x5C1720); } +WRAPPER RwFreeList* RwFreeListCreate(RwInt32 entrySize, RwInt32 entriesPerBlock, RwInt32 alignment) { EAXJMP(0x5C1790); } +WRAPPER RwInt32 RwFreeListPurge(RwFreeList* freelist) { EAXJMP(0x5C19F0); } +WRAPPER RwInt32 RwFreeListPurgeAllFreeLists() { EAXJMP(0x5C1B90); } +WRAPPER RwFreeList* RwFreeListForAllUsed(RwFreeList* freelist, RwFreeListCallBack fpCallBack, void* pData) { EAXJMP(0x5C1D40); } +WRAPPER RwBool _rxPipelineClose() { EAXJMP(0x5C2780); } +WRAPPER RwBool _rxPipelineOpen() { EAXJMP(0x5C27E0); } +WRAPPER RxHeap* RxHeapGetGlobalHeap() { EAXJMP(0x5C2AD0); } +WRAPPER RxPacket* RxPacketCreate(RxPipelineNode* node) { EAXJMP(0x5C2AE0); } +WRAPPER RxCluster* RxClusterSetExternalData(RxCluster* cluster, void* data, RwInt32 stride, RwInt32 numElements) { EAXJMP(0x5C2B10); } +WRAPPER RxCluster* RxClusterSetData(RxCluster* cluster, void* data, RwInt32 stride, RwInt32 numElements) { EAXJMP(0x5C2B70); } +WRAPPER RxCluster* RxClusterInitializeData(RxCluster* cluster, RwUInt32 numElements, RwUInt16 stride) { EAXJMP(0x5C2BD0); } +WRAPPER RxCluster* RxClusterResizeData(RxCluster* CurrentCluster, RwUInt32 NumElements) { EAXJMP(0x5C2C40); } +WRAPPER RxCluster* RxClusterLockWrite(RxPacket* packet, RwUInt32 clusterIndex, RxPipelineNode* node) { EAXJMP(0x5C2C90); } +WRAPPER RxPipeline* RxPipelineExecute(RxPipeline* pipeline, void* data, RwBool heapReset) { EAXJMP(0x5C2D60); } +WRAPPER RxPipeline* RxPipelineCreate() { EAXJMP(0x5C2E00); } +WRAPPER void _rxPipelineDestroy(RxPipeline* Pipeline) { EAXJMP(0x5C2E70); } +WRAPPER RwBool RwResourcesFreeResEntry(RwResEntry* entry) { EAXJMP(0x5C3080); } +WRAPPER void _rwResourcesPurge() { EAXJMP(0x5C30F0); } +WRAPPER RwResEntry* RwResourcesAllocateResEntry(void* owner, RwResEntry** ownerRef, RwInt32 size, RwResEntryDestroyNotify destroyNotify) { EAXJMP(0x5C3170); } +WRAPPER RwBool RwResourcesEmptyArena() { EAXJMP(0x5C3360); } +WRAPPER RwBool _rwPluginRegistryOpen() { EAXJMP(0x5C3450); } +WRAPPER RwBool _rwPluginRegistryClose() { EAXJMP(0x5C3480); } +WRAPPER RwInt32 _rwPluginRegistryGetPluginOffset(RwPluginRegistry const* reg, RwUInt32 pluginID) { EAXJMP(0x5C3590); } +WRAPPER RwInt32 _rwPluginRegistryAddPlugin(RwPluginRegistry* reg, RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5C35C0); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryInitObject(RwPluginRegistry const* reg, void* object) { EAXJMP(0x5C37F0); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryDeInitObject(RwPluginRegistry const* reg, void* object) { EAXJMP(0x5C3850); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryCopyObject(RwPluginRegistry const* reg, void* dstObject, void const* srcObject) { EAXJMP(0x5C3880); } +WRAPPER RwError* RwErrorSet(RwError* code) { EAXJMP(0x5C3910); } +WRAPPER RwInt32 _rwerror(RwInt32 code, ...) { EAXJMP(0x5C3970); } +WRAPPER RwInt32 _rwPluginRegistryAddPluginStream(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5C3980); } +WRAPPER RwInt32 _rwPluginRegistryAddPlgnStrmlwysCB(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB) { EAXJMP(0x5C39C0); } +WRAPPER RwInt32 _rwPluginRegistryAddPlgnStrmRightsCB(RwPluginRegistry* reg, RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB) { EAXJMP(0x5C39F0); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryReadDataChunks(RwPluginRegistry const* reg, RwStream* stream, void* object) { EAXJMP(0x5C3A20); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryInvokeRights(RwPluginRegistry const* reg, RwUInt32 id, void* obj, RwUInt32 extraData) { EAXJMP(0x5C3B50); } +WRAPPER RwInt32 _rwPluginRegistryGetSize(RwPluginRegistry const* reg, void const* object) { EAXJMP(0x5C3BA0); } +WRAPPER RwPluginRegistry const* _rwPluginRegistryWriteDataChunks(RwPluginRegistry const* reg, RwStream* stream, void const* object) { EAXJMP(0x5C3BE0); } +WRAPPER RwPluginRegistry const* _rwPluginRegistrySkipDataChunks(RwPluginRegistry const* reg, RwStream* stream) { EAXJMP(0x5C3CB0); } +WRAPPER RwCamera* RwCameraStreamRead(RwStream* stream) { EAXJMP(0x5C3D30); } +WRAPPER RwBBox* RwBBoxCalculate(RwBBox* boundBox, RwV3d const* verts, RwInt32 numVerts) { EAXJMP(0x5C5570); } +WRAPPER RwImage* RwImageResample(RwImage* dstImage, RwImage const* srcImage) { EAXJMP(0x5C72B0); } +WRAPPER RwImage* RwImageCreateResample(RwImage const* srcImage, RwInt32 width, RwInt32 height) { EAXJMP(0x5C7B30); } +WRAPPER RxRenderStateVector* RxRenderStateVectorSetDefaultRenderStateVector(RxRenderStateVector* rsvp) { EAXJMP(0x5D9240); } +WRAPPER RxRenderStateVector* RxRenderStateVectorCreate(RwBool current) { EAXJMP(0x5D9340); } +WRAPPER void RxRenderStateVectorDestroy(RxRenderStateVector* rsvp) { EAXJMP(0x5D9410); } +WRAPPER RxRenderStateVector* RxRenderStateVectorLoadDriverState(RxRenderStateVector* rsvp) { EAXJMP(0x5D9460); } +WRAPPER void _rxEmbeddedPacketBetweenPipelines(RxPipeline* fromPipeline, RxPipeline* toPipeline) { EAXJMP(0x5D95D0); } +WRAPPER RxPipelineNode* _rxEmbeddedPacketBetweenNodes(RxPipeline* pipeline, RxPipelineNode* nodeFrom, RwUInt32 whichOutput) { EAXJMP(0x5D9740); } +WRAPPER void _rxPacketDestroy(RxPacket* Packet) { EAXJMP(0x5D9810); } +WRAPPER RpMaterialList* _rpMaterialListDeinitialize(RpMaterialList* matList) { EAXJMP(0x5C8B10); } +WRAPPER RpMaterialList* _rpMaterialListInitialize(RpMaterialList* matList) { EAXJMP(0x5C8B70); } +WRAPPER RpMaterial* _rpMaterialListGetMaterial(RpMaterialList const* matList, RwInt32 matIndex) { EAXJMP(0x5C8B80); } +WRAPPER RwInt32 _rpMaterialListAppendMaterial(RpMaterialList* matList, RpMaterial* material) { EAXJMP(0x5C8B90); } +WRAPPER RwInt32 _rpMaterialListFindMaterialIndex(RpMaterialList const* matList, RpMaterial const* material) { EAXJMP(0x5C8C50); } +WRAPPER RpMaterialList* _rpMaterialListStreamRead(RwStream* stream, RpMaterialList* matList) { EAXJMP(0x5C8C80); } +WRAPPER RpMeshHeader* _rpMeshHeaderCreate(RwUInt32 size) { EAXJMP(0x5C8FE0); } +WRAPPER void* _rpMeshClose(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5C8FF0); } +WRAPPER void* _rpMeshOpen(void* instance, RwInt32 offset, RwInt32 size) { EAXJMP(0x5C9020); } +WRAPPER RpBuildMesh* _rpBuildMeshCreate(RwUInt32 bufferSize) { EAXJMP(0x5C9140); } +WRAPPER RwBool _rpBuildMeshDestroy(RpBuildMesh* mesh) { EAXJMP(0x5C9220); } +WRAPPER RwBool _rpMeshDestroy(RpMeshHeader* mesh) { EAXJMP(0x5C9260); } +WRAPPER RpBuildMesh* _rpBuildMeshAddTriangle(RpBuildMesh* mesh, RpMaterial* material, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3) { EAXJMP(0x5C92A0); } +WRAPPER RpMeshHeader* _rpMeshHeaderForAllMeshes(RpMeshHeader* meshHeader, RpMeshCallBack fpCallBack, void* pData) { EAXJMP(0x5C9380); } +WRAPPER RwStream* _rpMeshWrite(RpMeshHeader const* meshHeader, void const* object, RwStream* stream, RpMaterialList const* matList) { EAXJMP(0x5C93C0); } +WRAPPER RpMeshHeader* _rpMeshRead(RwStream* stream, void const* object, RpMaterialList const* matList) { EAXJMP(0x5C9510); } +WRAPPER RwInt32 _rpMeshSize(RpMeshHeader const* meshHeader, void const* object) { EAXJMP(0x5C96E0); } +WRAPPER RpMeshHeader* RpBuildMeshGenerateDefaultTriStrip(RpBuildMesh* buildmesh, void* data) { EAXJMP(0x5C9730); } +WRAPPER RpMeshHeader* _rpTriListMeshGenerate(RpBuildMesh* buildMesh, void* data) { EAXJMP(0x5CAE10); } +WRAPPER RpMeshHeader* _rpMeshOptimise(RpBuildMesh* buildmesh, RwUInt32 flags) { EAXJMP(0x5CB230); } +WRAPPER RwInt32 RpWorldSectorRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor ructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { EAXJMP(0x5CB2B0); } +WRAPPER RwInt32 RpWorldSectorRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) { EAXJMP(0x5CB2E0); } +WRAPPER RxPipeline* RpWorldSetDefaultSectorPipeline(RxPipeline* pipeline) { EAXJMP(0x5CB630); } +WRAPPER RxPipeline* RpAtomicSetDefaultPipeline(RxPipeline* pipeline) { EAXJMP(0x5CB670); } +WRAPPER void RpHAnimStdKeyFrameToMatrix(RwMatrix* matrix, void* voidIFrame) { EAXJMP(0x5CDEE0); } +WRAPPER void RpHAnimStdKeyFrameInterpolate(void* voidOut, void* voidIn1, void* voidIn2, RwReal time) { EAXJMP(0x5CE000); } +WRAPPER void RpHAnimStdKeyFrameBlend(void* voidOut, void* voidIn1, void* voidIn2, RwReal alpha) { EAXJMP(0x5CE420); } +WRAPPER RpHAnimAnimation* RpHAnimStdKeyFrameStreamRead(RwStream* stream, RpHAnimAnimation* animation) { EAXJMP(0x5CE820); } +WRAPPER RwBool RpHAnimStdKeyFrameStreamWrite(RpHAnimAnimation* animation, RwStream* stream) { EAXJMP(0x5CE8C0); } +WRAPPER RwInt32 RpHAnimStdKeyFrameStreamGetSize(RpHAnimAnimation* animation) { EAXJMP(0x5CE930); } +WRAPPER void RpHAnimStdKeyFrameMulRecip(void* voidFrame, void* voidStart) { EAXJMP(0x5CE950); } +WRAPPER void RpHAnimStdKeyFrameAdd(void* voidOut, void* voidIn1, void* voidIn2) { EAXJMP(0x5CEAB0); } +WRAPPER void RxHeapFree(RxHeap* heap, void* block) { EAXJMP(0x5D1070); } +WRAPPER void* RxHeapAlloc(RxHeap* heap, RwUInt32 size) { EAXJMP(0x5D1260); } +WRAPPER void* RxHeapRealloc(RxHeap* heap, void* block, RwUInt32 newSize, RwBool allowCopy) { EAXJMP(0x5D14D0); } +WRAPPER RwBool _rxHeapReset(RxHeap* heap) { EAXJMP(0x5D1680); } +WRAPPER void RxHeapDestroy(RxHeap* heap) { EAXJMP(0x5D16F0); } +WRAPPER RxHeap* RxHeapCreate(RwUInt32 size) { EAXJMP(0x5D1750); } +WRAPPER RxNodeOutput RxPipelineNodeFindOutputByName(RxPipelineNode* node, RwChar const* outputname) { EAXJMP(0x5D1EC0); } +WRAPPER RxNodeInput RxPipelineNodeFindInput(RxPipelineNode* node) { EAXJMP(0x5D1F20); } +WRAPPER RxPipeline* RxPipelineNodeRequestCluster(RxPipeline* pipeline, RxPipelineNode* node, RxClusterDefinition* clusterDef) { EAXJMP(0x5D1F30); } +WRAPPER RxPipeline* RxLockedPipeUnlock(RxLockedPipe* pipeline) { EAXJMP(0x5D1FA0); } +WRAPPER RxLockedPipe* RxPipelineLock(RxPipeline* pipeline) { EAXJMP(0x5D29F0); } +WRAPPER RxPipelineNode* RxPipelineFindNodeByName(RxPipeline* pipeline, RwChar const* name, RxPipelineNode* start, RwInt32* nodeIndex) { EAXJMP(0x5D2B10); } +WRAPPER RxLockedPipe* RxLockedPipeAddFragment(RxLockedPipe *pipeline, RwUInt32 *firstIndex, RxNodeDefinition *nodeDef0, ...) { EAXJMP(0x5D2BA0); } +WRAPPER RxPipeline* RxLockedPipeAddPath(RxLockedPipe* pipeline, RxNodeOutput out, RxNodeInput in) { EAXJMP(0x5D2EE0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmRenderSetup() { EAXJMP(0x5D31C0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmMangleTriangleIndices() { EAXJMP(0x5D35C0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetCullTriangle() { EAXJMP(0x5D3C60); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetClipTriangle() { EAXJMP(0x5D4F80); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetSubmitTriangle() { EAXJMP(0x5D51C0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmInstance() { EAXJMP(0x5D5400); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetTransform() { EAXJMP(0x5D6000); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmStash() { EAXJMP(0x5D61C0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetImmMangleLineIndices() { EAXJMP(0x5D6470); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetClipLine() { EAXJMP(0x5D7230); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetSubmitLine() { EAXJMP(0x5D74C0); } +WRAPPER RwBool _rwD3D8LightsOpen() { EAXJMP(0x5D9C90); } +WRAPPER void _rwD3D8LightsClose() { EAXJMP(0x5D9EF0); } +WRAPPER RwBool _rwD3D8LightsGlobalEnable(RpLightFlag flags) { EAXJMP(0x5D9F80); } +WRAPPER RwBool _rwD3D8LightLocalEnable(RpLight* light) { EAXJMP(0x5DA210); } +WRAPPER void _rwD3D8LightsEnable(RwBool enable, RwUInt32 type) { EAXJMP(0x5DA450); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetD3D8WorldSectorAllInOne() { EAXJMP(0x5DAAC0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetD3D8AtomicAllInOne() { EAXJMP(0x5DC500); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetWorldSectorInstance() { EAXJMP(0x5DCC50); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetWorldSectorEnumerateLights() { EAXJMP(0x5DCD80); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetAtomicInstance() { EAXJMP(0x5DD800); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetAtomicEnumerateLights() { EAXJMP(0x5DD9B0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetMaterialScatter() { EAXJMP(0x5DDAA0); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetLight() { EAXJMP(0x5DF040); } +WRAPPER RxNodeDefinition* RxNodeDefinitionGetPostLight() { EAXJMP(0x5DF560); } +WRAPPER void RxD3D8AllInOneSetRenderCallBack(RxPipelineNode* node, RxD3D8AllInOneRenderCallBack callback) { EAXJMP(0x5DFC60); }
\ No newline at end of file diff --git a/src/core/templates.h b/src/core/templates.h new file mode 100644 index 00000000..ef2db33a --- /dev/null +++ b/src/core/templates.h @@ -0,0 +1,205 @@ +#pragma once + +template<typename T, int n> +class CStore +{ +public: + int allocPtr; + T store[n]; + + T *alloc(void){ + if(this->allocPtr >= n){ + printf("Size of this thing:%d needs increasing\n", n); + assert(0); + } + return &this->store[this->allocPtr++]; + } + void clear(void){ + this->allocPtr = 0; + } +}; + +template<typename T, typename U = T> +class CPool +{ + U *m_entries; + union Flags { + struct { + uint8 id : 7; + uint8 free : 1; + }; + uint8 u; + } *m_flags; + int m_size; + int m_allocPtr; + +public: + CPool(int size){ + m_entries = (U*)malloc(sizeof(U)*size); + m_flags = (Flags*)malloc(sizeof(Flags)*size); + m_size = size; + m_allocPtr = 0; + for(int i = 0; i < size; i++){ + m_flags[i].id = 0; + m_flags[i].free = 1; + } + } + int GetSize(void) { return m_size; } + T *New(void){ + bool wrapped = false; + do + if(++m_allocPtr == m_size){ + if(wrapped) + return nil; + wrapped = true; + m_allocPtr = 0; + } + while(!m_flags[m_allocPtr].free); + m_flags[m_allocPtr].free = 0; + m_flags[m_allocPtr].id++; + return (T*)&m_entries[m_allocPtr]; + } + T *New(int handle){ + T *entry = (T*)&m_entries[handle>>8]; + SetNotFreeAt(handle); + return entry; + } + void SetNotFreeAt(int handle){ + int idx = handle>>8; + m_flags[idx].free = 0; + m_flags[idx].id = handle & 0x7F; + for(m_allocPtr = 0; m_allocPtr < m_size; m_allocPtr++) + if(m_flags[m_allocPtr].free) + return; + } + void Delete(T *entry){ + int i = GetJustIndex(entry); + m_flags[i].free = 1; + if(i < m_allocPtr) + m_allocPtr = i; + } + T *GetSlot(int i){ + return m_flags[i].free ? nil : (T*)&m_entries[i]; + } + T *GetAt(int handle){ + return m_flags[handle>>8].u == (handle & 0xFF) ? + (T*)&m_entries[handle >> 8] : nil; + } + int GetIndex(T *entry){ + int i = GetJustIndex(entry); + return m_flags[i].u + (i<<8); + } + int GetJustIndex(T *entry){ + // TODO: the cast is unsafe + return (int)((U*)entry - m_entries); + } + int GetNoOfUsedSpaces(void){ + int i; + int n = 0; + for(i = 0; i < m_size; i++) + if(!m_flags[i].free) + n++; + return n; + } + void ClearStorage(uint8 *&flags, U *&entries){ + free(flags); + free(entries); + flags = nil; + entries = nil; + } + void CopyBack(uint8 *&flags, U *&entries){ + memcpy(m_flags, flags, sizeof(uint8)*m_size); + memcpy(m_entries, entries, sizeof(U)*m_size); + debug("Size copied:%d (%d)\n", sizeof(U)*m_size, sizeof(Flags)*m_size); + m_allocPtr = 0; + ClearStorage(flags, entries); + debug("CopyBack:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */ + } + void Store(uint8 *&flags, U *&entries){ + flags = (uint8*)malloc(sizeof(uint8)*m_size); + entries = (U*)malloc(sizeof(U)*m_size); + memcpy(flags, m_flags, sizeof(uint8)*m_size); + memcpy(entries, m_entries, sizeof(U)*m_size); + debug("Stored:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */ + } +}; + +template<typename T> +class CLink +{ +public: + T item; + CLink<T> *prev; + CLink<T> *next; + + void Insert(CLink<T> *link){ + link->next = this->next; + this->next->prev = link; + link->prev = this; + this->next = link; + } + void Remove(void){ + this->prev->next = this->next; + this->next->prev = this->prev; + } +}; + +template<typename T> +class CLinkList +{ +public: + CLink<T> head, tail; + CLink<T> freeHead, freeTail; + CLink<T> *links; + + void Init(int n){ + links = new CLink<T>[n]; + head.next = &tail; + tail.prev = &head; + freeHead.next = &freeTail; + freeTail.prev = &freeHead; + while(n--) + freeHead.Insert(&links[n]); + } + void Shutdown(void){ + delete[] links; + links = nil; + } + void Clear(void){ + while(head.next != &tail) + Remove(head.next); + } + CLink<T> *Insert(T const &item){ + CLink<T> *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + head.Insert(node); + return node; + } + CLink<T> *InsertSorted(T const &item){ + CLink<T> *sort; + for(sort = head.next; sort != &tail; sort = sort->next) + if(sort->item.sort >= item.sort) + break; + CLink<T> *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + sort->prev->Insert(node); + return node; + } + void Remove(CLink<T> *link){ + link->Remove(); // remove from list + freeHead.Insert(link); // insert into free list + } + int Count(void){ + int n = 0; + CLink<T> *lnk; + for(lnk = head.next; lnk != &tail; lnk = lnk->next) + n++; + return n; + } +}; |