summaryrefslogtreecommitdiff
path: root/src/vehicles/HandlingMgr.cpp
diff options
context:
space:
mode:
authorclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
committerclaude-bot <[email protected]>2026-07-13 12:27:07 +0000
commit9f61c9e6ac6b1ac5692cf6352d2ebbd47a31a686 (patch)
treea84756b82513739a2672db3a1f0ec579db6d18ff /src/vehicles/HandlingMgr.cpp
downloadre3-miami.tar.gz
re3-miami.zip
Import Cai1Hsu/re3 @ miami (reVC / GTA:VC decompilation)HEADmiami
Snapshot import (no upstream history) into git.ancap.in.ua/claude, per @lzcnt. Source: https://github.com/Cai1Hsu/re3 branch miami.
Diffstat (limited to 'src/vehicles/HandlingMgr.cpp')
-rw-r--r--src/vehicles/HandlingMgr.cpp434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/vehicles/HandlingMgr.cpp b/src/vehicles/HandlingMgr.cpp
new file mode 100644
index 0000000..8438c5c
--- /dev/null
+++ b/src/vehicles/HandlingMgr.cpp
@@ -0,0 +1,434 @@
+#include "common.h"
+
+#include "main.h"
+#include "FileMgr.h"
+#include "Physical.h"
+#include "HandlingMgr.h"
+
+cHandlingDataMgr mod_HandlingManager;
+
+const char *HandlingFilename = "HANDLING.CFG";
+
+const char VehicleNames[NUMHANDLINGS][14] = {
+ "LANDSTAL",
+ "IDAHO",
+ "STINGER",
+ "LINERUN",
+ "PEREN",
+ "SENTINEL",
+ "PATRIOT",
+ "FIRETRUK",
+ "TRASH",
+ "STRETCH",
+ "MANANA",
+ "INFERNUS",
+ "PONY",
+ "MULE",
+ "CHEETAH",
+ "AMBULAN",
+ "FBICAR",
+ "MOONBEAM",
+ "ESPERANT",
+ "TAXI",
+ "KURUMA",
+ "BOBCAT",
+ "MRWHOOP",
+ "BFINJECT",
+ "POLICE",
+ "ENFORCER",
+ "SECURICA",
+ "BANSHEE",
+ "BUS",
+ "RHINO",
+ "BARRACKS",
+ "TRAIN",
+ "HELI",
+ "DODO",
+ "COACH",
+ "CABBIE",
+ "STALLION",
+ "RUMPO",
+ "RCBANDIT",
+ "MAFIA",
+ "AIRTRAIN",
+ "DEADDODO",
+ "FLATBED",
+ "YANKEE",
+ "GOLFCART",
+ "VOODOO",
+ "WASHING",
+ "CUBAN",
+ "ROMERO",
+ "PACKER",
+ "ADMIRAL",
+ "GANGBUR",
+ "ZEBRA",
+ "TOPFUN",
+ "GLENDALE",
+ "OCEANIC",
+ "HERMES",
+ "SABRE1",
+ "SABRETUR",
+ "PHEONIX",
+ "WALTON",
+ "REGINA",
+ "COMET",
+ "DELUXO",
+ "BURRITO",
+ "SPAND",
+ "BAGGAGE",
+ "KAUFMAN",
+ "RANCHER",
+ "FBIRANCH",
+ "VIRGO",
+ "GREENWOO",
+ "HOTRING",
+ "SANDKING",
+ "BLISTAC",
+ "BOXVILLE",
+ "BENSON",
+ "DESPERAD",
+ "LOVEFIST",
+ "BLOODRA",
+ "BLOODRB",
+ "BIKE",
+ "MOPED",
+ "DIRTBIKE",
+ "ANGEL",
+ "FREEWAY",
+ "PREDATOR",
+ "SPEEDER",
+ "REEFER",
+ "RIO",
+ "SQUALO",
+ "TROPIC",
+ "COASTGRD",
+ "DINGHY",
+ "MARQUIS",
+ "CUPBOAT",
+ "SEAPLANE",
+ "SPARROW",
+ "SEASPAR",
+ "MAVERICK",
+ "COASTMAV",
+ "POLMAV",
+ "HUNTER",
+ "RCBARON",
+ "RCGOBLIN",
+ "RCCOPTER"
+};
+
+cHandlingDataMgr::cHandlingDataMgr(void)
+{
+ memset(this, 0, sizeof(*this));
+}
+
+void
+cHandlingDataMgr::Initialise(void)
+{
+ LoadHandlingData();
+ field_0 = 0.1f;
+ fWheelFriction = 0.9f;
+ field_8 = 1.0f;
+ field_C = 0.8f;
+ field_10 = 0.98f;
+}
+
+void
+cHandlingDataMgr::LoadHandlingData(void)
+{
+ char *start, *end;
+ char line[201]; // weird value
+ char delim[4]; // not sure
+ char *word;
+ int field, handlingId;
+ int keepGoing;
+ tHandlingData *handling;
+ tFlyingHandlingData *flyingHandling;
+ tBoatHandlingData *boatHandling;
+ tBikeHandlingData *bikeHandling;
+
+ CFileMgr::SetDir("DATA");
+ CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r");
+ CFileMgr::SetDir("");
+
+ start = (char*)work_buff;
+ end = start+1;
+ handling = nil;
+ flyingHandling = nil;
+ boatHandling = nil;
+ bikeHandling = nil;
+ keepGoing = 1;
+
+ while(keepGoing){
+ // find end of line
+ while(*end != '\n') end++;
+
+ // get line
+ strncpy(line, start, end - start);
+ line[end - start] = '\0';
+ start = end+1;
+ end = start+1;
+
+ // yeah, this is kinda crappy
+ if(strcmp(line, ";the end") == 0)
+ keepGoing = 0;
+ else if(line[0] != ';'){
+ if(line[0] == '!'){
+ // Bike data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ bikeHandling = GetBikePointer(handlingId);
+ bikeHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: bikeHandling->fLeanFwdCOM = atof(word); break;
+ case 3: bikeHandling->fLeanFwdForce = atof(word); break;
+ case 4: bikeHandling->fLeanBakCOM = atof(word); break;
+ case 5: bikeHandling->fLeanBackForce = atof(word); break;
+ case 6: bikeHandling->fMaxLean = atof(word); break;
+ case 7: bikeHandling->fFullAnimLean = atof(word); break;
+ case 8: bikeHandling->fDesLean = atof(word); break;
+ case 9: bikeHandling->fSpeedSteer = atof(word); break;
+ case 10: bikeHandling->fSlipSteer = atof(word); break;
+ case 11: bikeHandling->fNoPlayerCOMz = atof(word); break;
+ case 12: bikeHandling->fWheelieAng = atof(word); break;
+ case 13: bikeHandling->fStoppieAng = atof(word); break;
+ case 14: bikeHandling->fWheelieSteer = atof(word); break;
+ case 15: bikeHandling->fWheelieStabMult = atof(word); break;
+ case 16: bikeHandling->fStoppieStabMult = atof(word); break;
+ }
+ field++;
+ }
+ ConvertBikeDataToGameUnits(bikeHandling);
+ }else if(line[0] == '$'){
+ // Flying data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ flyingHandling = GetFlyingPointer(handlingId);
+ flyingHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: flyingHandling->fThrust = atof(word); break;
+ case 3: flyingHandling->fThrustFallOff = atof(word); break;
+ case 4: flyingHandling->fYaw = atof(word); break;
+ case 5: flyingHandling->fYawStab = atof(word); break;
+ case 6: flyingHandling->fSideSlip = atof(word); break;
+ case 7: flyingHandling->fRoll = atof(word); break;
+ case 8: flyingHandling->fRollStab = atof(word); break;
+ case 9: flyingHandling->fPitch = atof(word); break;
+ case 10: flyingHandling->fPitchStab = atof(word); break;
+ case 11: flyingHandling->fFormLift = atof(word); break;
+ case 12: flyingHandling->fAttackLift = atof(word); break;
+ case 13: flyingHandling->fMoveRes = atof(word); break;
+ case 14: flyingHandling->vecTurnRes.x = atof(word); break;
+ case 15: flyingHandling->vecTurnRes.y = atof(word); break;
+ case 16: flyingHandling->vecTurnRes.z = atof(word); break;
+ case 17: flyingHandling->vecSpeedRes.x = atof(word); break;
+ case 18: flyingHandling->vecSpeedRes.y = atof(word); break;
+ case 19: flyingHandling->vecSpeedRes.z = atof(word); break;
+ }
+ field++;
+ }
+ }else if(line[0] == '%'){
+ // Boat data
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0: break;
+ case 1:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ boatHandling = GetBoatPointer(handlingId);
+ boatHandling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 2: boatHandling->fThrustY = atof(word); break;
+ case 3: boatHandling->fThrustZ = atof(word); break;
+ case 4: boatHandling->fThrustAppZ = atof(word); break;
+ case 5: boatHandling->fAqPlaneForce = atof(word); break;
+ case 6: boatHandling->fAqPlaneLimit = atof(word); break;
+ case 7: boatHandling->fAqPlaneOffset = atof(word); break;
+ case 8: boatHandling->fWaveAudioMult = atof(word); break;
+ case 9: boatHandling->vecMoveRes.x = atof(word); break;
+ case 10: boatHandling->vecMoveRes.y = atof(word); break;
+ case 11: boatHandling->vecMoveRes.z = atof(word); break;
+ case 12: boatHandling->vecTurnRes.x = atof(word); break;
+ case 13: boatHandling->vecTurnRes.y = atof(word); break;
+ case 14: boatHandling->vecTurnRes.z = atof(word); break;
+ case 15: boatHandling->fLook_L_R_BehindCamHeight = atof(word); break;
+ }
+ field++;
+ }
+ }else{
+ field = 0;
+ strcpy(delim, " \t");
+ // FIX: game seems to use a do-while loop here
+ for(word = strtok(line, delim); word; word = strtok(nil, delim)){
+ switch(field){
+ case 0:
+ handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
+ assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
+ handling = &HandlingData[handlingId];
+ handling->nIdentifier = (tVehicleType)handlingId;
+ break;
+ case 1: handling->fMass = atof(word); break;
+ case 2: handling->Dimension.x = atof(word); break;
+ case 3: handling->Dimension.y = atof(word); break;
+ case 4: handling->Dimension.z = atof(word); break;
+ case 5: handling->CentreOfMass.x = atof(word); break;
+ case 6: handling->CentreOfMass.y = atof(word); break;
+ case 7: handling->CentreOfMass.z = atof(word); break;
+ case 8: handling->nPercentSubmerged = atoi(word); break;
+ case 9: handling->fTractionMultiplier = atof(word); break;
+ case 10: handling->fTractionLoss = atof(word); break;
+ case 11: handling->fTractionBias = atof(word); break;
+ case 12: handling->Transmission.nNumberOfGears = atoi(word); break;
+ case 13: handling->Transmission.fMaxVelocity = atof(word); break;
+ case 14: handling->Transmission.fEngineAcceleration = atof(word) * 0.4; break;
+ case 15: handling->Transmission.nDriveType = word[0]; break;
+ case 16: handling->Transmission.nEngineType = word[0]; break;
+ case 17: handling->fBrakeDeceleration = atof(word); break;
+ case 18: handling->fBrakeBias = atof(word); break;
+ case 19: handling->bABS = !!atoi(word); break;
+ case 20: handling->fSteeringLock = atof(word); break;
+ case 21: handling->fSuspensionForceLevel = atof(word); break;
+ case 22: handling->fSuspensionDampingLevel = atof(word); break;
+ case 23: handling->fSeatOffsetDistance = atof(word); break;
+ case 24: handling->fCollisionDamageMultiplier = atof(word); break;
+ case 25: handling->nMonetaryValue = atoi(word); break;
+ case 26: handling->fSuspensionUpperLimit = atof(word); break;
+ case 27: handling->fSuspensionLowerLimit = atof(word); break;
+ case 28: handling->fSuspensionBias = atof(word); break;
+ case 29: handling->fSuspensionAntidiveMultiplier = atof(word); break;
+ case 30:
+ sscanf(word, "%x", &handling->Flags);
+ handling->Transmission.Flags = handling->Flags;
+ break;
+ case 31: handling->FrontLights = atoi(word); break;
+ case 32: handling->RearLights = atoi(word); break;
+ }
+ field++;
+ }
+ ConvertDataToGameUnits(handling);
+ }
+ }
+ }
+}
+
+int
+cHandlingDataMgr::FindExactWord(const char *word, const char *words, int wordLen, int numWords)
+{
+ int i;
+
+ for(i = 0; i < numWords; i++){
+ // BUG: the game does something really stupid here, it's fixed here
+ if(strncmp(word, words, wordLen) == 0)
+ return i;
+ words += wordLen;
+ }
+ return numWords;
+}
+
+
+void
+cHandlingDataMgr::ConvertDataToGameUnits(tHandlingData *handling)
+{
+ // convert distance to m, time to 1/50s
+ float velocity, a, b;
+
+ handling->Transmission.fEngineAcceleration *= 1.0f/(50.0f*50.0f);
+ handling->Transmission.fMaxVelocity *= 1000.0f/(60.0f*60.0f * 50.0f);
+ handling->fBrakeDeceleration *= 1.0f/(50.0f*50.0f);
+ handling->fTurnMass = (sq(handling->Dimension.x) + sq(handling->Dimension.y)) * handling->fMass / 12.0f;
+ if(handling->fTurnMass < 10.0f)
+ handling->fTurnMass *= 5.0f;
+ handling->fInvMass = 1.0f/handling->fMass;
+ handling->fCollisionDamageMultiplier *= 2000.0f/handling->fMass;
+ handling->fBuoyancy = 100.0f/handling->nPercentSubmerged * GRAVITY*handling->fMass;
+
+ // Don't quite understand this. What seems to be going on is that
+ // we calculate a drag (air resistance) deceleration for a given velocity and
+ // find the intersection between that and the max engine acceleration.
+ // at that point the car cannot accelerate any further and we've found the max velocity.
+ a = 0.0f;
+ b = 100.0f;
+ velocity = handling->Transmission.fMaxVelocity;
+ while(a < b && velocity > 0.0f){
+ velocity -= 0.01f;
+ // what's the 1/6?
+ a = handling->Transmission.fEngineAcceleration/6.0f;
+ // no density or drag coefficient here...
+ float a_drag = 0.5f*SQR(velocity) * handling->Dimension.x*handling->Dimension.z / handling->fMass;
+ // can't make sense of this... maybe v - v/(drag + 1) ? but that doesn't make so much sense either
+ b = -velocity * (1.0f/(a_drag + 1.0f) - 1.0f);
+ }
+
+ if(handling->nIdentifier == HANDLING_RCBANDIT){
+ handling->Transmission.fMaxCruiseVelocity = handling->Transmission.fMaxVelocity;
+ handling->Transmission.fMaxReverseVelocity = -handling->Transmission.fMaxVelocity;
+ }else if(handling->nIdentifier >= HANDLING_BIKE && handling->nIdentifier <= HANDLING_FREEWAY){
+ handling->Transmission.fMaxCruiseVelocity = velocity;
+ handling->Transmission.fMaxVelocity = velocity * 1.2f;
+ handling->Transmission.fMaxReverseVelocity = -0.05f;
+ }else{
+ handling->Transmission.fMaxCruiseVelocity = velocity;
+ handling->Transmission.fMaxVelocity = velocity * 1.2f;
+ handling->Transmission.fMaxReverseVelocity = -0.2f;
+ }
+
+ if(handling->Transmission.nDriveType == '4')
+ handling->Transmission.fEngineAcceleration /= 4.0f;
+ else
+ handling->Transmission.fEngineAcceleration /= 2.0f;
+
+ handling->Transmission.InitGearRatios();
+}
+
+void
+cHandlingDataMgr::ConvertBikeDataToGameUnits(tBikeHandlingData *handling)
+{
+ handling->fMaxLean = Sin(DEGTORAD(handling->fMaxLean));
+ handling->fFullAnimLean = DEGTORAD(handling->fFullAnimLean);
+ handling->fWheelieAng = Sin(DEGTORAD(handling->fWheelieAng));
+ handling->fStoppieAng = Sin(DEGTORAD(handling->fStoppieAng));
+}
+
+int32
+cHandlingDataMgr::GetHandlingId(const char *name)
+{
+ int i;
+ for(i = 0; i < NUMHANDLINGS; i++)
+ if(strncmp(VehicleNames[i], name, 14) == 0)
+ break;
+ return i;
+}
+
+tFlyingHandlingData*
+cHandlingDataMgr::GetFlyingPointer(uint8 id)
+{
+ if(id >= HANDLING_SEAPLANE && id <= HANDLING_RCCOPTER)
+ return &FlyingHandlingData[id-HANDLING_SEAPLANE];
+ return &FlyingHandlingData[0];
+}
+
+tBoatHandlingData*
+cHandlingDataMgr::GetBoatPointer(uint8 id)
+{
+ if(id >= HANDLING_PREDATOR && id <= HANDLING_SEAPLANE)
+ return &BoatHandlingData[id-HANDLING_PREDATOR];
+ return &BoatHandlingData[0];
+}