diff --git a/rwengine/src/engine/GameState.hpp b/rwengine/src/engine/GameState.hpp index a4df8ccd..91bc63c1 100644 --- a/rwengine/src/engine/GameState.hpp +++ b/rwengine/src/engine/GameState.hpp @@ -69,17 +69,17 @@ struct BasicState BasicState (); }; -/** Block 15 player info */ +/** Block 16 player info */ struct PlayerInfo { - uint16_t money; + uint32_t money; uint8_t unknown1; - uint16_t unknown2; - uint8_t unknown3; + uint32_t unknown2; + uint16_t unknown3; float unknown4; - uint16_t displayedMoney; - uint16_t hiddenPackagesCollected; - uint16_t hiddenPackageCount; + uint32_t displayedMoney; + uint32_t hiddenPackagesCollected; + uint32_t hiddenPackageCount; uint8_t neverTired; uint8_t fastReload; uint8_t thaneOfLibertyCity; @@ -92,26 +92,26 @@ struct PlayerInfo /** Block 17 */ struct GameStats { - uint16_t playerKills; - uint16_t otherKills; - uint16_t carsExploded; - uint16_t shotsHit; - uint16_t pedTypesKilled[23]; - uint16_t helicoptersDestroyed; - uint16_t playerProgress; - uint16_t explosiveKgsUsed; - uint16_t bulletsFired; - uint16_t bulletsHit; - uint16_t carsCrushed; - uint16_t headshots; - uint16_t timesBusted; - uint16_t timesHospital; - uint16_t daysPassed; - uint16_t mmRainfall; - uint16_t insaneJumpMaxDistance; - uint16_t insaneJumpMaxHeight; - uint16_t insaneJumpMaxFlips; - uint16_t insangeJumpMaxRotation; + uint32_t playerKills; + uint32_t otherKills; + uint32_t carsExploded; + uint32_t shotsHit; + uint32_t pedTypesKilled[23]; + uint32_t helicoptersDestroyed; + uint32_t playerProgress; + uint32_t explosiveKgsUsed; + uint32_t bulletsFired; + uint32_t bulletsHit; + uint32_t carsCrushed; + uint32_t headshots; + uint32_t timesBusted; + uint32_t timesHospital; + uint32_t daysPassed; + uint32_t mmRainfall; + uint32_t insaneJumpMaxDistance; + uint32_t insaneJumpMaxHeight; + uint32_t insaneJumpMaxFlips; + uint32_t insangeJumpMaxRotation; /* * 0 none completed * 1 insane stunt @@ -123,36 +123,36 @@ struct GameStats * 7 quadruple * 8 perfect quadruple */ - uint16_t bestStunt; - uint16_t uniqueStuntsFound; - uint16_t uniqueStuntsTotal; - uint16_t missionAttempts; - uint16_t missionsPassed; - uint16_t passengersDroppedOff; - uint16_t taxiRevenue; - uint16_t portlandPassed; - uint16_t stauntonPassed; - uint16_t shoresidePassed; - uint16_t bestTurismoTime; + uint32_t bestStunt; + uint32_t uniqueStuntsFound; + uint32_t uniqueStuntsTotal; + uint32_t missionAttempts; + uint32_t missionsPassed; + uint32_t passengersDroppedOff; + uint32_t taxiRevenue; + uint32_t portlandPassed; + uint32_t stauntonPassed; + uint32_t shoresidePassed; + uint32_t bestTurismoTime; float distanceWalked; float distanceDriven; - uint16_t patriotPlaygroundTime; - uint16_t aRideInTheParkTime; - uint16_t grippedTime; - uint16_t multistoryMayhemTime; - uint16_t peopleSaved; - uint16_t criminalsKilled; - uint16_t highestParamedicLevel; - uint16_t firesExtinguished; - uint16_t longestDodoFlight; - uint16_t bombDefusalTime; - uint16_t rampagesPassed; - uint16_t totalRampages; - uint16_t totalMissions; - uint16_t fastestTime[16]; // not used - uint16_t highestScore[16]; - uint16_t peopleKilledSinceCheckpoint; // ? - uint16_t peopleKilledSinceLastBustedOrWasted; + uint32_t patriotPlaygroundTime; + uint32_t aRideInTheParkTime; + uint32_t grippedTime; + uint32_t multistoryMayhemTime; + uint32_t peopleSaved; + uint32_t criminalsKilled; + uint32_t highestParamedicLevel; + uint32_t firesExtinguished; + uint32_t longestDodoFlight; + uint32_t bombDefusalTime; + uint32_t rampagesPassed; + uint32_t totalRampages; + uint32_t totalMissions; + uint32_t fastestTime[16]; // not used + uint32_t highestScore[16]; + uint32_t peopleKilledSinceCheckpoint; // ? + uint32_t peopleKilledSinceLastBustedOrWasted; char lastMissionGXT[8]; GameStats (); diff --git a/rwgame/DrawUI.cpp b/rwgame/DrawUI.cpp index 70ebdfdc..87285696 100644 --- a/rwgame/DrawUI.cpp +++ b/rwgame/DrawUI.cpp @@ -86,7 +86,12 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re infoTextY += ui_textHeight; - ti.text = GameSymbols::Money + GameStringUtil::fromString(std::to_string(world->state->playerInfo.money)); + { + std::stringstream ss; + ss << std::setw(8) << std::setfill('0') << world->state->playerInfo.displayedMoney; + + ti.text = GameSymbols::Money + GameStringUtil::fromString(ss.str()); + } ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(infoTextX + 1.f, infoTextY+1.f); render->text.renderText(ti); diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index bcd191f9..6bfce6e3 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "GitSHA1.h" @@ -271,6 +272,176 @@ PlayerController *RWGame::getPlayer() return nullptr; } +// Modifiers for GTA3 we try to recreate +#define RW_GAME_VERSION 1100 +#define RW_GAME_GTA3_GERMAN 0 +#define RW_GAME_GTA3_ANNIVERSARY 0 + +void RWGame::handleCheatInput(char symbol) { + cheatInputWindow = cheatInputWindow.substr(1) + symbol; + + // Helper to check for cheats + auto checkForCheat = [this](std::string cheat, std::function action) { + RW_CHECK(cheatInputWindow.length() >= cheat.length(), "Cheat too long"); + size_t offset = cheatInputWindow.length() - cheat.length(); + if (cheat == cheatInputWindow.substr(offset)) { + log.info("Game", "Cheat triggered: '" + cheat + "'"); + if (action) { + action(); + } + } + }; + + // Player related cheats + { + auto player = static_cast(world->pedestrianPool.find(state->playerObject)); + +#ifdef RW_GAME_GTA3_GERMAN // Germans got their own cheat + std::string health_cheat = "GESUNDHEIT"; +#else + std::string health_cheat = "HEALTH"; +#endif + checkForCheat(health_cheat, [&]{ + player->getCurrentState().health = 100.f; + // @todo ShowHelpMessage("CHEAT3"); // III / VC: Inputting health cheat. + }); + +#if RW_GAME_VERSION >= 1100 // Changed cheat name in version 1.1 + std::string armor_cheat = "TORTOISE"; +#else + std::string armor_cheat = "TURTOISE"; +#endif + checkForCheat(armor_cheat, [&]{ + player->getCurrentState().armour = 100.f; + // @todo ShowHelpMessage("CHEAT4"); // III / VC: Inputting armor cheat. + }); + + checkForCheat("GUNSGUNSGUNS", [&]{ + // @todo give player weapons + // @todo ShowHelpMessage("CHEAT2"); // III / VC: Inputting weapon cheats. + }); + + checkForCheat("IFIWEREARICHMAN", [&]{ + world->state->playerInfo.money += 250000; + // @todo ShowHelpMessage("CHEAT6"); // III: Inputting money cheat. + }); + + checkForCheat("MOREPOLICEPLEASE", [&]{ + // @todo raise to next wanted level + // @todo ShowHelpMessage("CHEAT5"); // III / VC: Inputting wanted level cheats. + }); + + checkForCheat("NOPOLICEPLEASE", [&]{ + // @todo lower to next lower wanted level + // @todo ShowHelpMessage("CHEAT5"); // III / VC: Inputting wanted level cheats. + }); + } + + // Misc cheats. + { + checkForCheat("BANGBANGBANG", [&]{ + // @todo Explode nearby vehicles + // @todo What radius? + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("GIVEUSATANK", [&]{ + // The iPod / Android version of the game (10th year anniversary) spawns random (?) vehicles instead of always rhino +#ifdef RW_GAME_GTA3_ANNIVERSARY + uint16_t vehicleModel = 110; // @todo Which cars are spawned?! +#else + uint16_t vehicleModel = 122; +#endif + // @todo Spawn rhino + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("CORNERSLIKEMAD", [&]{ + // @todo Weird car handling + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("ANICESETOFWHEELS", [&]{ + // @todo Hide car bodies + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("CHITTYCHITTYBB", [&]{ + // @todo Cars can fly + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("NASTYLIMBCHEAT", [&]{ + // @todo Makes it possible to shoot limbs off, iirc? + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("ILIKEDRESSINGUP", [&]{ + // @todo Which skins will be chosen? + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + } + + // Pedestrian cheats + { + checkForCheat("WEAPONSFORALL", [&]{ + // @todo Give all pedestrians weapons.. this is also saved in the savegame?! + // @todo Which weapons do they get? + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("NOBODYLIKESME", [&]{ + // @todo Set all pedestrians hostile towards player.. this is also saved in the savegame?! + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("ITSALLGOINGMAAAD", [&]{ + // @todo Set all pedestrians to fighting each other.. this is also saved in the savegame?! + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + // Game speed cheats + + checkForCheat("TIMEFLIESWHENYOU", [&]{ + // @todo Set fast gamespeed + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + + checkForCheat("BOOOOORING", [&]{ + // @todo Set slow gamespeed + // @todo ShowHelpMessage("CHEAT1"); // III / VC: Inputting most cheats. + }); + } + + // Weather cheats + { + checkForCheat("ILIKESCOTLAND", [&]{ + // @todo Set weather to cloudy + // @todo ShowHelpMessage("CHEAT7"); // III / VC: Inputting weather cheats. + }); + + checkForCheat("SKINCANCERFORME", [&]{ + // @todo Set sunny / normal weather + // @todo ShowHelpMessage("CHEAT7"); // III / VC: Inputting weather cheats. + }); + + checkForCheat("MADWEATHER", [&]{ + // @todo Set bad weather + // @todo ShowHelpMessage("CHEAT7"); // III / VC: Inputting weather cheats. + }); + + checkForCheat("ILOVESCOTLAND", [&]{ + // @todo Set weather to rainy + // @todo ShowHelpMessage("CHEAT7"); // III / VC: Inputting weather cheats. + }); + + checkForCheat("PEASOUP", [&]{ + // @todo Set weather to foggy + // @todo ShowHelpMessage("CHEAT7"); // III / VC: Inputting weather cheats. + }); + } +} + int RWGame::run() { last_clock_time = clock.now(); @@ -797,4 +968,10 @@ void RWGame::globalKeyEvent(const SDL_Event& event) break; default: break; } + + std::string keyName = SDL_GetKeyName(event.key.keysym.sym); + if (keyName.length() == 1) { + char symbol = keyName[0]; + handleCheatInput(symbol); + } } diff --git a/rwgame/RWGame.hpp b/rwgame/RWGame.hpp index c785e2f6..2ac6ac09 100644 --- a/rwgame/RWGame.hpp +++ b/rwgame/RWGame.hpp @@ -39,6 +39,8 @@ class RWGame bool showDebugPhysics = false; int lastDraws; /// Number of draws issued for the last frame. + std::string cheatInputWindow = std::string(32, ' '); + float accum = 0.f; float timescale = 1.f; public: @@ -136,6 +138,8 @@ private: void renderDebugPaths(float time); void renderProfile(); + void handleCheatInput(char symbol); + void globalKeyEvent(const SDL_Event& event); }; diff --git a/rwgame/states/IngameState.cpp b/rwgame/states/IngameState.cpp index 09557fbc..7d174d36 100644 --- a/rwgame/states/IngameState.cpp +++ b/rwgame/states/IngameState.cpp @@ -174,10 +174,39 @@ void IngameState::exit() void IngameState::tick(float dt) { + auto world = getWorld(); autolookTimer = std::max(autolookTimer - dt, 0.f); + // Update displayed money value + // @todo the game uses another algorithm which is non-linear + { + float moneyFrequency = 1.0f / 30.0f; + moneyTimer += dt; + while (moneyTimer >= moneyFrequency) { + int32_t difference = world->state->playerInfo.money - world->state->playerInfo.displayedMoney; + + // Generates 0, 1 (difference < 100), 12 (difference < 1000), 123 (difference < 10000), .. etc. + // Negative input will result in negative output + auto GetIncrement = [](int32_t difference) -> int32_t { + // @todo is this correct for difference >= 1000000000 ? + int32_t r = 1; + int32_t i = 2; + if (difference == 0) { return 0; } + while (std::abs(difference) >= 100) { + difference /= 10; + r = r * 10 + i; + i++; + } + return (difference < 0) ? -r : r; + }; + world->state->playerInfo.displayedMoney += GetIncrement(difference); + + moneyTimer -= moneyFrequency; + } + } + auto player = game->getPlayer(); - const auto& input = getWorld()->state->input; + const auto& input = world->state->input; if( player && player->isInputEnabled() ) { float viewDistance = 4.f; @@ -199,7 +228,7 @@ void IngameState::tick(float dt) viewDistance = 4.f; } - auto target = getWorld()->pedestrianPool.find(getWorld()->state->cameraTarget); + auto target = world->pedestrianPool.find(world->state->cameraTarget); if( target == nullptr ) { @@ -374,7 +403,7 @@ void IngameState::tick(float dt) auto from = btVector3(rayStart.x, rayStart.y, rayStart.z); ClosestNotMeRayResultCallback ray(physTarget, from, to); - getWorld()->dynamicsWorld->rayTest(from, to, ray); + world->dynamicsWorld->rayTest(from, to, ray); if( ray.hasHit() && ray.m_closestHitFraction < 1.f ) { cameraPosition = glm::vec3(ray.m_hitPointWorld.x(), ray.m_hitPointWorld.y(), diff --git a/rwgame/states/IngameState.hpp b/rwgame/states/IngameState.hpp index 3061c5d8..8319f05b 100644 --- a/rwgame/states/IngameState.hpp +++ b/rwgame/states/IngameState.hpp @@ -32,6 +32,8 @@ class IngameState : public State bool m_invertedY; /// Free look in vehicles. bool m_vehicleFreeLook; + + float moneyTimer; // Timer used to updated displayed money value public: /** * @brief IngameState